Keep integer seconds since the Unix epoch,
together with integer nanoseconds in the 0 <= n < 1e9 range.
For now, nanoseconds are still always zero.
This commit is about data structure changes.
( )
| Alphare |
| hg-reviewers |
Keep integer seconds since the Unix epoch,
together with integer nanoseconds in the 0 <= n < 1e9 range.
For now, nanoseconds are still always zero.
This commit is about data structure changes.
| No Linters Available |
| No Unit Test Coverage |
| Path | Packages | |||
|---|---|---|---|---|
| M | mercurial/cext/parsers.c (86 lines) | |||
| M | mercurial/cext/util.h (3 lines) | |||
| M | mercurial/dirstate.py (34 lines) | |||
| M | mercurial/dirstatemap.py (1 line) | |||
| A | M | mercurial/dirstateutils/timestamp.py (53 lines) | ||
| M | mercurial/dirstateutils/v2.py (8 lines) | |||
| M | mercurial/merge.py (7 lines) | |||
| M | mercurial/pure/parsers.py (49 lines) | |||
| M | rust/hg-core/src/dirstate/entry.rs (53 lines) | |||
| M | rust/hg-core/src/dirstate/parsers.rs (3 lines) | |||
| M | rust/hg-core/src/dirstate/status.rs (3 lines) | |||
| M | rust/hg-core/src/dirstate_tree/dirstate_map.rs (9 lines) | |||
| M | rust/hg-core/src/dirstate_tree/on_disk.rs (17 lines) | |||
| M | rust/hg-core/src/dirstate_tree/status.rs (3 lines) | |||
| M | rust/hg-cpython/src/dirstate.rs (2 lines) | |||
| M | rust/hg-cpython/src/dirstate/dirstate_map.rs (11 lines) | |||
| M | rust/hg-cpython/src/dirstate/item.rs (31 lines) | |||
| M | rust/hg-cpython/src/dirstate/status.rs (4 lines) | |||
| M | rust/rhg/src/commands/status.rs (3 lines) | |||
| M | tests/fakedirstatewritetime.py (5 lines) |
| Commit | Parents | Author | Summary | Date |
|---|---|---|---|---|
| 86265d82dfbb | b56858d85a7b | Simon Sapin | Oct 18 2021, 5:23 AM |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | marmoute | ||
| Closed | SimonSapin |
| dirstateItemObject *t; | dirstateItemObject *t; | ||||
| int wc_tracked; | int wc_tracked; | ||||
| int p1_tracked; | int p1_tracked; | ||||
| int p2_info; | int p2_info; | ||||
| int has_meaningful_data; | int has_meaningful_data; | ||||
| int has_meaningful_mtime; | int has_meaningful_mtime; | ||||
| int mode; | int mode; | ||||
| int size; | int size; | ||||
| int mtime; | int mtime_s; | ||||
| int mtime_ns; | |||||
| PyObject *parentfiledata; | PyObject *parentfiledata; | ||||
| static char *keywords_name[] = { | static char *keywords_name[] = { | ||||
| "wc_tracked", | "wc_tracked", | ||||
| "p1_tracked", | "p1_tracked", | ||||
| "p2_info", | "p2_info", | ||||
| "has_meaningful_data", | "has_meaningful_data", | ||||
| "has_meaningful_mtime", | "has_meaningful_mtime", | ||||
| "parentfiledata", | "parentfiledata", | ||||
| if (p1_tracked) { | if (p1_tracked) { | ||||
| t->flags |= dirstate_flag_p1_tracked; | t->flags |= dirstate_flag_p1_tracked; | ||||
| } | } | ||||
| if (p2_info) { | if (p2_info) { | ||||
| t->flags |= dirstate_flag_p2_info; | t->flags |= dirstate_flag_p2_info; | ||||
| } | } | ||||
| if (parentfiledata != Py_None) { | if (parentfiledata != Py_None) { | ||||
| if (!PyTuple_CheckExact(parentfiledata)) { | if (!PyArg_ParseTuple(parentfiledata, "ii(ii)", &mode, &size, | ||||
| PyErr_SetString( | &mtime_s, &mtime_ns)) { | ||||
| PyExc_TypeError, | |||||
| "parentfiledata should be a Tuple or None"); | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0)); | |||||
| size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1)); | |||||
| mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2)); | |||||
| } else { | } else { | ||||
| has_meaningful_data = 0; | has_meaningful_data = 0; | ||||
| has_meaningful_mtime = 0; | has_meaningful_mtime = 0; | ||||
| } | } | ||||
| if (has_meaningful_data) { | if (has_meaningful_data) { | ||||
| t->flags |= dirstate_flag_has_meaningful_data; | t->flags |= dirstate_flag_has_meaningful_data; | ||||
| t->mode = mode; | t->mode = mode; | ||||
| t->size = size; | t->size = size; | ||||
| } else { | } else { | ||||
| t->mode = 0; | t->mode = 0; | ||||
| t->size = 0; | t->size = 0; | ||||
| } | } | ||||
| if (has_meaningful_mtime) { | if (has_meaningful_mtime) { | ||||
| t->flags |= dirstate_flag_has_file_mtime; | t->flags |= dirstate_flag_has_file_mtime; | ||||
| t->mtime = mtime; | t->mtime_s = mtime_s; | ||||
| t->mtime_ns = mtime_ns; | |||||
| } else { | } else { | ||||
| t->mtime = 0; | t->mtime_s = 0; | ||||
| t->mtime_ns = 0; | |||||
| } | } | ||||
| return (PyObject *)t; | return (PyObject *)t; | ||||
| } | } | ||||
| static void dirstate_item_dealloc(PyObject *o) | static void dirstate_item_dealloc(PyObject *o) | ||||
| { | { | ||||
| PyObject_Del(o); | PyObject_Del(o); | ||||
| } | } | ||||
| if (dirstate_item_c_removed(self)) { | if (dirstate_item_c_removed(self)) { | ||||
| return 0; | return 0; | ||||
| } else if (!(self->flags & dirstate_flag_has_file_mtime) || | } else if (!(self->flags & dirstate_flag_has_file_mtime) || | ||||
| !(self->flags & dirstate_flag_p1_tracked) || | !(self->flags & dirstate_flag_p1_tracked) || | ||||
| !(self->flags & dirstate_flag_wc_tracked) || | !(self->flags & dirstate_flag_wc_tracked) || | ||||
| (self->flags & dirstate_flag_p2_info)) { | (self->flags & dirstate_flag_p2_info)) { | ||||
| return ambiguous_time; | return ambiguous_time; | ||||
| } else { | } else { | ||||
| return self->mtime; | return self->mtime_s; | ||||
| } | } | ||||
| } | } | ||||
| static PyObject *dirstate_item_v2_data(dirstateItemObject *self) | static PyObject *dirstate_item_v2_data(dirstateItemObject *self) | ||||
| { | { | ||||
| unsigned char flags = self->flags; | unsigned char flags = self->flags; | ||||
| int mode = dirstate_item_c_v1_mode(self); | int mode = dirstate_item_c_v1_mode(self); | ||||
| if ((mode & S_IXUSR) != 0) { | if ((mode & S_IXUSR) != 0) { | ||||
| flags |= dirstate_flag_mode_exec_perm; | flags |= dirstate_flag_mode_exec_perm; | ||||
| } else { | } else { | ||||
| flags &= ~dirstate_flag_mode_exec_perm; | flags &= ~dirstate_flag_mode_exec_perm; | ||||
| } | } | ||||
| if (S_ISLNK(mode)) { | if (S_ISLNK(mode)) { | ||||
| flags |= dirstate_flag_mode_is_symlink; | flags |= dirstate_flag_mode_is_symlink; | ||||
| } else { | } else { | ||||
| flags &= ~dirstate_flag_mode_is_symlink; | flags &= ~dirstate_flag_mode_is_symlink; | ||||
| } | } | ||||
| return Py_BuildValue("Bii", flags, self->size, self->mtime); | return Py_BuildValue("Biii", flags, self->size, self->mtime_s, | ||||
| self->mtime_ns); | |||||
| }; | }; | ||||
| static PyObject *dirstate_item_v1_state(dirstateItemObject *self) | static PyObject *dirstate_item_v1_state(dirstateItemObject *self) | ||||
| { | { | ||||
| char state = dirstate_item_c_v1_state(self); | char state = dirstate_item_c_v1_state(self); | ||||
| return PyBytes_FromStringAndSize(&state, 1); | return PyBytes_FromStringAndSize(&state, 1); | ||||
| }; | }; | ||||
| static PyObject *dirstate_item_v1_mode(dirstateItemObject *self) | static PyObject *dirstate_item_v1_mode(dirstateItemObject *self) | ||||
| { | { | ||||
| return PyInt_FromLong(dirstate_item_c_v1_mode(self)); | return PyInt_FromLong(dirstate_item_c_v1_mode(self)); | ||||
| }; | }; | ||||
| static PyObject *dirstate_item_v1_size(dirstateItemObject *self) | static PyObject *dirstate_item_v1_size(dirstateItemObject *self) | ||||
| { | { | ||||
| return PyInt_FromLong(dirstate_item_c_v1_size(self)); | return PyInt_FromLong(dirstate_item_c_v1_size(self)); | ||||
| }; | }; | ||||
| static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self) | static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self) | ||||
| { | { | ||||
| return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); | return PyInt_FromLong(dirstate_item_c_v1_mtime(self)); | ||||
| }; | }; | ||||
| static PyObject *dirstate_item_need_delay(dirstateItemObject *self, | static PyObject *dirstate_item_need_delay(dirstateItemObject *self, | ||||
| PyObject *value) | PyObject *now) | ||||
| { | { | ||||
| long now; | int now_s; | ||||
| if (!pylong_to_long(value, &now)) { | int now_ns; | ||||
| if (!PyArg_ParseTuple(now, "ii", &now_s, &now_ns)) { | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| if (dirstate_item_c_v1_state(self) == 'n' && | if (dirstate_item_c_v1_state(self) == 'n' && self->mtime_s == now_s && | ||||
| dirstate_item_c_v1_mtime(self) == now) { | self->mtime_ns == now_ns) { | ||||
| Py_RETURN_TRUE; | |||||
| } else { | |||||
| Py_RETURN_FALSE; | |||||
| } | |||||
| }; | |||||
| static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self, | |||||
| PyObject *other) | |||||
| { | |||||
| int other_s; | |||||
| int other_ns; | |||||
| if (!PyArg_ParseTuple(other, "ii", &other_s, &other_ns)) { | |||||
| return NULL; | |||||
| } | |||||
| if ((self->flags & dirstate_flag_has_file_mtime) && | |||||
| self->mtime_s == other_s && self->mtime_ns == other_ns) { | |||||
| Py_RETURN_TRUE; | Py_RETURN_TRUE; | ||||
| } else { | } else { | ||||
| Py_RETURN_FALSE; | Py_RETURN_FALSE; | ||||
| } | } | ||||
| }; | }; | ||||
| /* This will never change since it's bound to V1 | /* This will never change since it's bound to V1 | ||||
| */ | */ | ||||
| static inline dirstateItemObject * | static inline dirstateItemObject * | ||||
| dirstate_item_from_v1_data(char state, int mode, int size, int mtime) | dirstate_item_from_v1_data(char state, int mode, int size, int mtime) | ||||
| { | { | ||||
| dirstateItemObject *t = | dirstateItemObject *t = | ||||
| PyObject_New(dirstateItemObject, &dirstateItemType); | PyObject_New(dirstateItemObject, &dirstateItemType); | ||||
| if (!t) { | if (!t) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| t->flags = 0; | t->flags = 0; | ||||
| t->mode = 0; | t->mode = 0; | ||||
| t->size = 0; | t->size = 0; | ||||
| t->mtime = 0; | t->mtime_s = 0; | ||||
| t->mtime_ns = 0; | |||||
| if (state == 'm') { | if (state == 'm') { | ||||
| t->flags = (dirstate_flag_wc_tracked | | t->flags = (dirstate_flag_wc_tracked | | ||||
| dirstate_flag_p1_tracked | dirstate_flag_p2_info); | dirstate_flag_p1_tracked | dirstate_flag_p2_info); | ||||
| } else if (state == 'a') { | } else if (state == 'a') { | ||||
| t->flags = dirstate_flag_wc_tracked; | t->flags = dirstate_flag_wc_tracked; | ||||
| } else if (state == 'r') { | } else if (state == 'r') { | ||||
| if (size == dirstate_v1_nonnormal) { | if (size == dirstate_v1_nonnormal) { | ||||
| t->size = size; | t->size = size; | ||||
| } else { | } else { | ||||
| t->flags = (dirstate_flag_wc_tracked | | t->flags = (dirstate_flag_wc_tracked | | ||||
| dirstate_flag_p1_tracked | | dirstate_flag_p1_tracked | | ||||
| dirstate_flag_has_meaningful_data | | dirstate_flag_has_meaningful_data | | ||||
| dirstate_flag_has_file_mtime); | dirstate_flag_has_file_mtime); | ||||
| t->mode = mode; | t->mode = mode; | ||||
| t->size = size; | t->size = size; | ||||
| t->mtime = mtime; | t->mtime_s = mtime; | ||||
| } | } | ||||
| } else { | } else { | ||||
| PyErr_Format(PyExc_RuntimeError, | PyErr_Format(PyExc_RuntimeError, | ||||
| "unknown state: `%c` (%d, %d, %d)", state, mode, | "unknown state: `%c` (%d, %d, %d)", state, mode, | ||||
| size, mtime, NULL); | size, mtime, NULL); | ||||
| Py_DECREF(t); | Py_DECREF(t); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| static PyObject *dirstate_item_from_v2_meth(PyTypeObject *subtype, | static PyObject *dirstate_item_from_v2_meth(PyTypeObject *subtype, | ||||
| PyObject *args) | PyObject *args) | ||||
| { | { | ||||
| dirstateItemObject *t = | dirstateItemObject *t = | ||||
| PyObject_New(dirstateItemObject, &dirstateItemType); | PyObject_New(dirstateItemObject, &dirstateItemType); | ||||
| if (!t) { | if (!t) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| if (!PyArg_ParseTuple(args, "bii", &t->flags, &t->size, &t->mtime)) { | if (!PyArg_ParseTuple(args, "biii", &t->flags, &t->size, &t->mtime_s, | ||||
| &t->mtime_ns)) { | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| t->mode = 0; | t->mode = 0; | ||||
| if (t->flags & dirstate_flag_has_meaningful_data) { | if (t->flags & dirstate_flag_has_meaningful_data) { | ||||
| if (t->flags & dirstate_flag_mode_exec_perm) { | if (t->flags & dirstate_flag_mode_exec_perm) { | ||||
| t->mode = 0755; | t->mode = 0755; | ||||
| } else { | } else { | ||||
| t->mode = 0644; | t->mode = 0644; | ||||
| self->flags &= ~dirstate_flag_has_file_mtime; | self->flags &= ~dirstate_flag_has_file_mtime; | ||||
| Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
| } | } | ||||
| /* See docstring of the python implementation for details */ | /* See docstring of the python implementation for details */ | ||||
| static PyObject *dirstate_item_set_clean(dirstateItemObject *self, | static PyObject *dirstate_item_set_clean(dirstateItemObject *self, | ||||
| PyObject *args) | PyObject *args) | ||||
| { | { | ||||
| int size, mode, mtime; | int size, mode, mtime_s, mtime_ns; | ||||
| if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) { | if (!PyArg_ParseTuple(args, "ii(ii)", &mode, &size, &mtime_s, | ||||
| &mtime_ns)) { | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | | self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked | | ||||
| dirstate_flag_has_meaningful_data | | dirstate_flag_has_meaningful_data | | ||||
| dirstate_flag_has_file_mtime; | dirstate_flag_has_file_mtime; | ||||
| self->mode = mode; | self->mode = mode; | ||||
| self->size = size; | self->size = size; | ||||
| self->mtime = mtime; | self->mtime_s = mtime_s; | ||||
| self->mtime_ns = mtime_ns; | |||||
| Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
| } | } | ||||
| static PyObject *dirstate_item_set_tracked(dirstateItemObject *self) | static PyObject *dirstate_item_set_tracked(dirstateItemObject *self) | ||||
| { | { | ||||
| self->flags |= dirstate_flag_wc_tracked; | self->flags |= dirstate_flag_wc_tracked; | ||||
| self->flags &= ~dirstate_flag_has_file_mtime; | self->flags &= ~dirstate_flag_has_file_mtime; | ||||
| Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
| } | } | ||||
| static PyObject *dirstate_item_set_untracked(dirstateItemObject *self) | static PyObject *dirstate_item_set_untracked(dirstateItemObject *self) | ||||
| { | { | ||||
| self->flags &= ~dirstate_flag_wc_tracked; | self->flags &= ~dirstate_flag_wc_tracked; | ||||
| self->mode = 0; | self->mode = 0; | ||||
| self->mtime = 0; | |||||
| self->size = 0; | self->size = 0; | ||||
| self->mtime_s = 0; | |||||
| self->mtime_ns = 0; | |||||
| Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
| } | } | ||||
| static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self) | static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self) | ||||
| { | { | ||||
| if (self->flags & dirstate_flag_p2_info) { | if (self->flags & dirstate_flag_p2_info) { | ||||
| self->flags &= ~(dirstate_flag_p2_info | | self->flags &= ~(dirstate_flag_p2_info | | ||||
| dirstate_flag_has_meaningful_data | | dirstate_flag_has_meaningful_data | | ||||
| dirstate_flag_has_file_mtime); | dirstate_flag_has_file_mtime); | ||||
| self->mode = 0; | self->mode = 0; | ||||
| self->mtime = 0; | |||||
| self->size = 0; | self->size = 0; | ||||
| self->mtime_s = 0; | |||||
| self->mtime_ns = 0; | |||||
| } | } | ||||
| Py_RETURN_NONE; | Py_RETURN_NONE; | ||||
| } | } | ||||
| static PyMethodDef dirstate_item_methods[] = { | static PyMethodDef dirstate_item_methods[] = { | ||||
| {"v2_data", (PyCFunction)dirstate_item_v2_data, METH_NOARGS, | {"v2_data", (PyCFunction)dirstate_item_v2_data, METH_NOARGS, | ||||
| "return data suitable for v2 serialization"}, | "return data suitable for v2 serialization"}, | ||||
| {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS, | {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS, | ||||
| "return a \"state\" suitable for v1 serialization"}, | "return a \"state\" suitable for v1 serialization"}, | ||||
| {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS, | {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS, | ||||
| "return a \"mode\" suitable for v1 serialization"}, | "return a \"mode\" suitable for v1 serialization"}, | ||||
| {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS, | {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS, | ||||
| "return a \"size\" suitable for v1 serialization"}, | "return a \"size\" suitable for v1 serialization"}, | ||||
| {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS, | {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS, | ||||
| "return a \"mtime\" suitable for v1 serialization"}, | "return a \"mtime\" suitable for v1 serialization"}, | ||||
| {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O, | {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O, | ||||
| "True if the stored mtime would be ambiguous with the current time"}, | "True if the stored mtime would be ambiguous with the current time"}, | ||||
| {"mtime_likely_equal_to", (PyCFunction)dirstate_item_mtime_likely_equal_to, | |||||
| METH_O, "True if the stored mtime is likely equal to the given mtime"}, | |||||
| {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, | {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth, | ||||
| METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"}, | METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"}, | ||||
| {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth, | {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth, | ||||
| METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V2 data"}, | METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V2 data"}, | ||||
| {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty, | {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty, | ||||
| METH_NOARGS, "mark a file as \"possibly dirty\""}, | METH_NOARGS, "mark a file as \"possibly dirty\""}, | ||||
| {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS, | {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS, | ||||
| "mark a file as \"clean\""}, | "mark a file as \"clean\""}, | ||||
| */ | */ | ||||
| static PyObject *pack_dirstate(PyObject *self, PyObject *args) | static PyObject *pack_dirstate(PyObject *self, PyObject *args) | ||||
| { | { | ||||
| PyObject *packobj = NULL; | PyObject *packobj = NULL; | ||||
| PyObject *map, *copymap, *pl, *mtime_unset = NULL; | PyObject *map, *copymap, *pl, *mtime_unset = NULL; | ||||
| Py_ssize_t nbytes, pos, l; | Py_ssize_t nbytes, pos, l; | ||||
| PyObject *k, *v = NULL, *pn; | PyObject *k, *v = NULL, *pn; | ||||
| char *p, *s; | char *p, *s; | ||||
| int now; | int now_s; | ||||
| int now_ns; | |||||
| if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map, | if (!PyArg_ParseTuple(args, "O!O!O!(ii):pack_dirstate", &PyDict_Type, | ||||
| &PyDict_Type, ©map, &PyTuple_Type, &pl, | &map, &PyDict_Type, ©map, &PyTuple_Type, &pl, | ||||
| &now)) { | &now_s, &now_ns)) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| if (PyTuple_Size(pl) != 2) { | if (PyTuple_Size(pl) != 2) { | ||||
| PyErr_SetString(PyExc_TypeError, "expected 2-element tuple"); | PyErr_SetString(PyExc_TypeError, "expected 2-element tuple"); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| goto bail; | goto bail; | ||||
| } | } | ||||
| tuple = (dirstateItemObject *)v; | tuple = (dirstateItemObject *)v; | ||||
| state = dirstate_item_c_v1_state(tuple); | state = dirstate_item_c_v1_state(tuple); | ||||
| mode = dirstate_item_c_v1_mode(tuple); | mode = dirstate_item_c_v1_mode(tuple); | ||||
| size = dirstate_item_c_v1_size(tuple); | size = dirstate_item_c_v1_size(tuple); | ||||
| mtime = dirstate_item_c_v1_mtime(tuple); | mtime = dirstate_item_c_v1_mtime(tuple); | ||||
| if (state == 'n' && mtime == now) { | if (state == 'n' && tuple->mtime_s == now_s && | ||||
| tuple->mtime_ns == now_ns) { | |||||
| /* See pure/parsers.py:pack_dirstate for why we do | /* See pure/parsers.py:pack_dirstate for why we do | ||||
| * this. */ | * this. */ | ||||
| mtime = -1; | mtime = -1; | ||||
| mtime_unset = (PyObject *)dirstate_item_from_v1_data( | mtime_unset = (PyObject *)dirstate_item_from_v1_data( | ||||
| state, mode, size, mtime); | state, mode, size, mtime); | ||||
| if (!mtime_unset) { | if (!mtime_unset) { | ||||
| goto bail; | goto bail; | ||||
| } | } | ||||
| #endif | #endif | ||||
| /* clang-format off */ | /* clang-format off */ | ||||
| typedef struct { | typedef struct { | ||||
| PyObject_HEAD | PyObject_HEAD | ||||
| unsigned char flags; | unsigned char flags; | ||||
| int mode; | int mode; | ||||
| int size; | int size; | ||||
| int mtime; | int mtime_s; | ||||
| int mtime_ns; | |||||
| } dirstateItemObject; | } dirstateItemObject; | ||||
| /* clang-format on */ | /* clang-format on */ | ||||
| static const unsigned char dirstate_flag_wc_tracked = 1; | static const unsigned char dirstate_flag_wc_tracked = 1; | ||||
| static const unsigned char dirstate_flag_p1_tracked = 1 << 1; | static const unsigned char dirstate_flag_p1_tracked = 1 << 1; | ||||
| static const unsigned char dirstate_flag_p2_info = 1 << 2; | static const unsigned char dirstate_flag_p2_info = 1 << 2; | ||||
| static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3; | static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3; | ||||
| static const unsigned char dirstate_flag_has_file_mtime = 1 << 4; | static const unsigned char dirstate_flag_has_file_mtime = 1 << 4; | ||||
| """record that the current state of the file on disk is unknown""" | """record that the current state of the file on disk is unknown""" | ||||
| entry = self[filename] | entry = self[filename] | ||||
| entry.set_possibly_dirty() | entry.set_possibly_dirty() | ||||
| self._refresh_entry(filename, entry) | self._refresh_entry(filename, entry) | ||||
| def set_clean(self, filename, mode, size, mtime): | def set_clean(self, filename, mode, size, mtime): | ||||
| """mark a file as back to a clean state""" | """mark a file as back to a clean state""" | ||||
| entry = self[filename] | entry = self[filename] | ||||
| mtime = mtime & rangemask | |||||
| size = size & rangemask | size = size & rangemask | ||||
| entry.set_clean(mode, size, mtime) | entry.set_clean(mode, size, mtime) | ||||
| self._refresh_entry(filename, entry) | self._refresh_entry(filename, entry) | ||||
| self.copymap.pop(filename, None) | self.copymap.pop(filename, None) | ||||
| def set_tracked(self, filename): | def set_tracked(self, filename): | ||||
| new = False | new = False | ||||
| entry = self.get(filename) | entry = self.get(filename) | ||||
| # Copyright Mercurial Contributors | |||||
| # | |||||
| # This software may be used and distributed according to the terms of the | |||||
| # GNU General Public License version 2 or any later version. | |||||
| from __future__ import absolute_import | |||||
| import stat | |||||
| rangemask = 0x7FFFFFFF | |||||
| class timestamp(tuple): | |||||
| """ | |||||
| A Unix timestamp with nanoseconds precision, | |||||
| modulo 2**31 seconds. | |||||
| A 2-tuple containing: | |||||
| `truncated_seconds`: seconds since the Unix epoch, | |||||
| truncated to its lower 31 bits | |||||
| `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. | |||||
| """ | |||||
| def __new__(cls, value): | |||||
| truncated_seconds, subsec_nanos = value | |||||
| value = (truncated_seconds & rangemask, subsec_nanos) | |||||
| return super(timestamp, cls).__new__(cls, value) | |||||
| def zero(): | |||||
| """ | |||||
| Returns the `timestamp` at the Unix epoch. | |||||
| """ | |||||
| return tuple.__new__(timestamp, (0, 0)) | |||||
| def mtime_of(stat_result): | |||||
| """ | |||||
| Takes an `os.stat_result`-like object and returns a `timestamp` object | |||||
| for its modification time. | |||||
| """ | |||||
| # https://docs.python.org/2/library/os.html#os.stat_float_times | |||||
| # "For compatibility with older Python versions, | |||||
| # accessing stat_result as a tuple always returns integers." | |||||
| secs = stat_result[stat.ST_MTIME] | |||||
| # For now | |||||
| subsec_nanos = 0 | |||||
| return timestamp((secs, subsec_nanos)) | |||||
| copy_source_len, | copy_source_len, | ||||
| children_start, | children_start, | ||||
| children_count, | children_count, | ||||
| _descendants_with_entry_count, | _descendants_with_entry_count, | ||||
| _tracked_descendants_count, | _tracked_descendants_count, | ||||
| flags, | flags, | ||||
| size, | size, | ||||
| mtime_s, | mtime_s, | ||||
| _mtime_ns, | mtime_ns, | ||||
| ) = NODE.unpack(node_bytes) | ) = NODE.unpack(node_bytes) | ||||
| # Parse child nodes of this node recursively | # Parse child nodes of this node recursively | ||||
| parse_nodes(map, copy_map, data, children_start, children_count) | parse_nodes(map, copy_map, data, children_start, children_count) | ||||
| item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s) | item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s, mtime_ns) | ||||
| if not item.any_tracked: | if not item.any_tracked: | ||||
| continue | continue | ||||
| path = slice_with_len(data, path_start, path_len) | path = slice_with_len(data, path_start, path_len) | ||||
| map[path] = item | map[path] = item | ||||
| if copy_source_start: | if copy_source_start: | ||||
| copy_map[path] = slice_with_len( | copy_map[path] = slice_with_len( | ||||
| data, copy_source_start, copy_source_len | data, copy_source_start, copy_source_len | ||||
| ) | ) | ||||
| basename_start = path.rfind(b'/') + 1 # 0 if rfind returns -1 | basename_start = path.rfind(b'/') + 1 # 0 if rfind returns -1 | ||||
| if copy is not None: | if copy is not None: | ||||
| copy_source_start = paths_offset + len(path) | copy_source_start = paths_offset + len(path) | ||||
| copy_source_len = len(copy) | copy_source_len = len(copy) | ||||
| else: | else: | ||||
| copy_source_start = 0 | copy_source_start = 0 | ||||
| copy_source_len = 0 | copy_source_len = 0 | ||||
| if entry is not None: | if entry is not None: | ||||
| flags, size, mtime_s = entry.v2_data() | flags, size, mtime_s, mtime_ns = entry.v2_data() | ||||
| mtime_ns = 0 | |||||
| else: | else: | ||||
| # There are no mtime-cached directories in the Python implementation | # There are no mtime-cached directories in the Python implementation | ||||
| flags = 0 | flags = 0 | ||||
| size = 0 | size = 0 | ||||
| mtime_s = 0 | mtime_s = 0 | ||||
| mtime_ns = 0 | mtime_ns = 0 | ||||
| return NODE.pack( | return NODE.pack( | ||||
| path_start, | path_start, | ||||
| that order | that order | ||||
| ## Special case for the root node | ## Special case for the root node | ||||
| The root node is not serialized in the format, but its information is | The root node is not serialized in the format, but its information is | ||||
| written to the docket. Again, see more details on the on-disk format in | written to the docket. Again, see more details on the on-disk format in | ||||
| `mercurial/helptext/internals/dirstate-v2`. | `mercurial/helptext/internals/dirstate-v2`. | ||||
| """ | """ | ||||
| now = int(now) | |||||
| data = bytearray() | data = bytearray() | ||||
| root_nodes_start = 0 | root_nodes_start = 0 | ||||
| root_nodes_len = 0 | root_nodes_len = 0 | ||||
| nodes_with_entry_count = 0 | nodes_with_entry_count = 0 | ||||
| nodes_with_copy_source_count = 0 | nodes_with_copy_source_count = 0 | ||||
| # Will always be 0 since this implementation always re-writes everything | # Will always be 0 since this implementation always re-writes everything | ||||
| # to disk | # to disk | ||||
| unreachable_bytes = 0 | unreachable_bytes = 0 | ||||
| # merge.py - directory-level update/merge handling for Mercurial | # merge.py - directory-level update/merge handling for Mercurial | ||||
| # | # | ||||
| # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com> | # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com> | ||||
| # | # | ||||
| # This software may be used and distributed according to the terms of the | # This software may be used and distributed according to the terms of the | ||||
| # GNU General Public License version 2 or any later version. | # GNU General Public License version 2 or any later version. | ||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||
| import collections | import collections | ||||
| import errno | import errno | ||||
| import stat | |||||
| import struct | import struct | ||||
| from .i18n import _ | from .i18n import _ | ||||
| from .node import nullrev | from .node import nullrev | ||||
| from .thirdparty import attr | from .thirdparty import attr | ||||
| from .utils import stringutil | from .utils import stringutil | ||||
| from .dirstateutils import timestamp | |||||
| from . import ( | from . import ( | ||||
| copies, | copies, | ||||
| encoding, | encoding, | ||||
| error, | error, | ||||
| filemerge, | filemerge, | ||||
| match as matchmod, | match as matchmod, | ||||
| mergestate as mergestatemod, | mergestate as mergestatemod, | ||||
| obsutil, | obsutil, | ||||
| fctx(f).data(), | fctx(f).data(), | ||||
| flags, | flags, | ||||
| backgroundclose=True, | backgroundclose=True, | ||||
| atomictemp=atomictemp, | atomictemp=atomictemp, | ||||
| ) | ) | ||||
| if wantfiledata: | if wantfiledata: | ||||
| s = wfctx.lstat() | s = wfctx.lstat() | ||||
| mode = s.st_mode | mode = s.st_mode | ||||
| mtime = s[stat.ST_MTIME] | mtime = timestamp.mtime_of(s) | ||||
| filedata[f] = (mode, size, mtime) # for dirstate.normal | # for dirstate.update_file's parentfiledata argument: | ||||
| filedata[f] = (mode, size, mtime) | |||||
| if i == 100: | if i == 100: | ||||
| yield False, (i, f) | yield False, (i, f) | ||||
| i = 0 | i = 0 | ||||
| i += 1 | i += 1 | ||||
| if i > 0: | if i > 0: | ||||
| yield False, (i, f) | yield False, (i, f) | ||||
| yield True, filedata | yield True, filedata | ||||
| yet. | yet. | ||||
| """ | """ | ||||
| _wc_tracked = attr.ib() | _wc_tracked = attr.ib() | ||||
| _p1_tracked = attr.ib() | _p1_tracked = attr.ib() | ||||
| _p2_info = attr.ib() | _p2_info = attr.ib() | ||||
| _mode = attr.ib() | _mode = attr.ib() | ||||
| _size = attr.ib() | _size = attr.ib() | ||||
| _mtime = attr.ib() | _mtime_s = attr.ib() | ||||
| _mtime_ns = attr.ib() | |||||
| def __init__( | def __init__( | ||||
| self, | self, | ||||
| wc_tracked=False, | wc_tracked=False, | ||||
| p1_tracked=False, | p1_tracked=False, | ||||
| p2_info=False, | p2_info=False, | ||||
| has_meaningful_data=True, | has_meaningful_data=True, | ||||
| has_meaningful_mtime=True, | has_meaningful_mtime=True, | ||||
| parentfiledata=None, | parentfiledata=None, | ||||
| ): | ): | ||||
| self._wc_tracked = wc_tracked | self._wc_tracked = wc_tracked | ||||
| self._p1_tracked = p1_tracked | self._p1_tracked = p1_tracked | ||||
| self._p2_info = p2_info | self._p2_info = p2_info | ||||
| self._mode = None | self._mode = None | ||||
| self._size = None | self._size = None | ||||
| self._mtime = None | self._mtime_s = None | ||||
| self._mtime_ns = None | |||||
| if parentfiledata is None: | if parentfiledata is None: | ||||
| has_meaningful_mtime = False | has_meaningful_mtime = False | ||||
| has_meaningful_data = False | has_meaningful_data = False | ||||
| if has_meaningful_data: | if has_meaningful_data: | ||||
| self._mode = parentfiledata[0] | self._mode = parentfiledata[0] | ||||
| self._size = parentfiledata[1] | self._size = parentfiledata[1] | ||||
| if has_meaningful_mtime: | if has_meaningful_mtime: | ||||
| self._mtime = parentfiledata[2] | self._mtime_s, self._mtime_ns = parentfiledata[2] | ||||
| @classmethod | @classmethod | ||||
| def from_v2_data(cls, flags, size, mtime): | def from_v2_data(cls, flags, size, mtime_s, mtime_ns): | ||||
| """Build a new DirstateItem object from V2 data""" | """Build a new DirstateItem object from V2 data""" | ||||
| has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) | has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) | ||||
| mode = None | mode = None | ||||
| if has_mode_size: | if has_mode_size: | ||||
| assert stat.S_IXUSR == 0o100 | assert stat.S_IXUSR == 0o100 | ||||
| if flags & DIRSTATE_V2_MODE_EXEC_PERM: | if flags & DIRSTATE_V2_MODE_EXEC_PERM: | ||||
| mode = 0o755 | mode = 0o755 | ||||
| else: | else: | ||||
| mode = 0o644 | mode = 0o644 | ||||
| if flags & DIRSTATE_V2_MODE_IS_SYMLINK: | if flags & DIRSTATE_V2_MODE_IS_SYMLINK: | ||||
| mode |= stat.S_IFLNK | mode |= stat.S_IFLNK | ||||
| else: | else: | ||||
| mode |= stat.S_IFREG | mode |= stat.S_IFREG | ||||
| return cls( | return cls( | ||||
| wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), | wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), | ||||
| p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), | p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), | ||||
| p2_info=bool(flags & DIRSTATE_V2_P2_INFO), | p2_info=bool(flags & DIRSTATE_V2_P2_INFO), | ||||
| has_meaningful_data=has_mode_size, | has_meaningful_data=has_mode_size, | ||||
| has_meaningful_mtime=bool(flags & DIRSTATE_V2_HAS_FILE_MTIME), | has_meaningful_mtime=bool(flags & DIRSTATE_V2_HAS_FILE_MTIME), | ||||
| parentfiledata=(mode, size, mtime), | parentfiledata=(mode, size, (mtime_s, mtime_ns)), | ||||
| ) | ) | ||||
| @classmethod | @classmethod | ||||
| def from_v1_data(cls, state, mode, size, mtime): | def from_v1_data(cls, state, mode, size, mtime): | ||||
| """Build a new DirstateItem object from V1 data | """Build a new DirstateItem object from V1 data | ||||
| Since the dirstate-v1 format is frozen, the signature of this function | Since the dirstate-v1 format is frozen, the signature of this function | ||||
| is not expected to change, unlike the __init__ one. | is not expected to change, unlike the __init__ one. | ||||
| return cls(wc_tracked=True, p2_info=True) | return cls(wc_tracked=True, p2_info=True) | ||||
| elif size == NONNORMAL: | elif size == NONNORMAL: | ||||
| return cls(wc_tracked=True, p1_tracked=True) | return cls(wc_tracked=True, p1_tracked=True) | ||||
| elif mtime == AMBIGUOUS_TIME: | elif mtime == AMBIGUOUS_TIME: | ||||
| return cls( | return cls( | ||||
| wc_tracked=True, | wc_tracked=True, | ||||
| p1_tracked=True, | p1_tracked=True, | ||||
| has_meaningful_mtime=False, | has_meaningful_mtime=False, | ||||
| parentfiledata=(mode, size, 42), | parentfiledata=(mode, size, (42, 0)), | ||||
| ) | ) | ||||
| else: | else: | ||||
| return cls( | return cls( | ||||
| wc_tracked=True, | wc_tracked=True, | ||||
| p1_tracked=True, | p1_tracked=True, | ||||
| parentfiledata=(mode, size, mtime), | parentfiledata=(mode, size, (mtime, 0)), | ||||
| ) | ) | ||||
| else: | else: | ||||
| raise RuntimeError(b'unknown state: %s' % state) | raise RuntimeError(b'unknown state: %s' % state) | ||||
| def set_possibly_dirty(self): | def set_possibly_dirty(self): | ||||
| """Mark a file as "possibly dirty" | """Mark a file as "possibly dirty" | ||||
| This means the next status call will have to actually check its content | This means the next status call will have to actually check its content | ||||
| to make sure it is correct. | to make sure it is correct. | ||||
| """ | """ | ||||
| self._mtime = None | self._mtime_s = None | ||||
| self._mtime_ns = None | |||||
| def set_clean(self, mode, size, mtime): | def set_clean(self, mode, size, mtime): | ||||
| """mark a file as "clean" cancelling potential "possibly dirty call" | """mark a file as "clean" cancelling potential "possibly dirty call" | ||||
| Note: this function is a descendant of `dirstate.normal` and is | Note: this function is a descendant of `dirstate.normal` and is | ||||
| currently expected to be call on "normal" entry only. There are not | currently expected to be call on "normal" entry only. There are not | ||||
| reason for this to not change in the future as long as the ccode is | reason for this to not change in the future as long as the ccode is | ||||
| updated to preserve the proper state of the non-normal files. | updated to preserve the proper state of the non-normal files. | ||||
| """ | """ | ||||
| self._wc_tracked = True | self._wc_tracked = True | ||||
| self._p1_tracked = True | self._p1_tracked = True | ||||
| self._mode = mode | self._mode = mode | ||||
| self._size = size | self._size = size | ||||
| self._mtime = mtime | self._mtime_s, self._mtime_ns = mtime | ||||
| def set_tracked(self): | def set_tracked(self): | ||||
| """mark a file as tracked in the working copy | """mark a file as tracked in the working copy | ||||
| This will ultimately be called by command like `hg add`. | This will ultimately be called by command like `hg add`. | ||||
| """ | """ | ||||
| self._wc_tracked = True | self._wc_tracked = True | ||||
| # `set_tracked` is replacing various `normallookup` call. So we mark | # `set_tracked` is replacing various `normallookup` call. So we mark | ||||
| # the files as needing lookup | # the files as needing lookup | ||||
| # | # | ||||
| # Consider dropping this in the future in favor of something less broad. | # Consider dropping this in the future in favor of something less broad. | ||||
| self._mtime = None | self._mtime_s = None | ||||
| self._mtime_ns = None | |||||
| def set_untracked(self): | def set_untracked(self): | ||||
| """mark a file as untracked in the working copy | """mark a file as untracked in the working copy | ||||
| This will ultimately be called by command like `hg remove`. | This will ultimately be called by command like `hg remove`. | ||||
| """ | """ | ||||
| self._wc_tracked = False | self._wc_tracked = False | ||||
| self._mode = None | self._mode = None | ||||
| self._size = None | self._size = None | ||||
| self._mtime = None | self._mtime_s = None | ||||
| self._mtime_ns = None | |||||
| def drop_merge_data(self): | def drop_merge_data(self): | ||||
| """remove all "merge-only" from a DirstateItem | """remove all "merge-only" from a DirstateItem | ||||
| This is to be call by the dirstatemap code when the second parent is dropped | This is to be call by the dirstatemap code when the second parent is dropped | ||||
| """ | """ | ||||
| if self._p2_info: | if self._p2_info: | ||||
| self._p2_info = False | self._p2_info = False | ||||
| self._mode = None | self._mode = None | ||||
| self._size = None | self._size = None | ||||
| self._mtime = None | self._mtime_s = None | ||||
| self._mtime_ns = None | |||||
| @property | @property | ||||
| def mode(self): | def mode(self): | ||||
| return self.v1_mode() | return self.v1_mode() | ||||
| @property | @property | ||||
| def size(self): | def size(self): | ||||
| return self.v1_size() | return self.v1_size() | ||||
| @property | @property | ||||
| def mtime(self): | def mtime(self): | ||||
| return self.v1_mtime() | return self.v1_mtime() | ||||
| def mtime_likely_equal_to(self, other_mtime): | |||||
| self_sec = self._mtime_s | |||||
| if self_sec is None: | |||||
| return False | |||||
| self_ns = self._mtime_ns | |||||
| other_sec, other_ns = other_mtime | |||||
| return self_sec == other_sec and self_ns == other_ns | |||||
| @property | @property | ||||
| def state(self): | def state(self): | ||||
| """ | """ | ||||
| States are: | States are: | ||||
| n normal | n normal | ||||
| m needs merging | m needs merging | ||||
| r marked for removal | r marked for removal | ||||
| a marked for addition | a marked for addition | ||||
| if self._p2_info: | if self._p2_info: | ||||
| flags |= DIRSTATE_V2_P2_INFO | flags |= DIRSTATE_V2_P2_INFO | ||||
| if self._mode is not None and self._size is not None: | if self._mode is not None and self._size is not None: | ||||
| flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE | flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE | ||||
| if self.mode & stat.S_IXUSR: | if self.mode & stat.S_IXUSR: | ||||
| flags |= DIRSTATE_V2_MODE_EXEC_PERM | flags |= DIRSTATE_V2_MODE_EXEC_PERM | ||||
| if stat.S_ISLNK(self.mode): | if stat.S_ISLNK(self.mode): | ||||
| flags |= DIRSTATE_V2_MODE_IS_SYMLINK | flags |= DIRSTATE_V2_MODE_IS_SYMLINK | ||||
| if self._mtime is not None: | if self._mtime_s is not None: | ||||
| flags |= DIRSTATE_V2_HAS_FILE_MTIME | flags |= DIRSTATE_V2_HAS_FILE_MTIME | ||||
| return (flags, self._size or 0, self._mtime or 0) | return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0) | ||||
| def v1_state(self): | def v1_state(self): | ||||
| """return a "state" suitable for v1 serialization""" | """return a "state" suitable for v1 serialization""" | ||||
| if not self.any_tracked: | if not self.any_tracked: | ||||
| # the object has no state to record, this is -currently- | # the object has no state to record, this is -currently- | ||||
| # unsupported | # unsupported | ||||
| raise RuntimeError('untracked item') | raise RuntimeError('untracked item') | ||||
| elif self.removed: | elif self.removed: | ||||
| def v1_mtime(self): | def v1_mtime(self): | ||||
| """return a "mtime" suitable for v1 serialization""" | """return a "mtime" suitable for v1 serialization""" | ||||
| if not self.any_tracked: | if not self.any_tracked: | ||||
| # the object has no state to record, this is -currently- | # the object has no state to record, this is -currently- | ||||
| # unsupported | # unsupported | ||||
| raise RuntimeError('untracked item') | raise RuntimeError('untracked item') | ||||
| elif self.removed: | elif self.removed: | ||||
| return 0 | return 0 | ||||
| elif self._mtime is None: | elif self._mtime_s is None: | ||||
| return AMBIGUOUS_TIME | return AMBIGUOUS_TIME | ||||
| elif self._p2_info: | elif self._p2_info: | ||||
| return AMBIGUOUS_TIME | return AMBIGUOUS_TIME | ||||
| elif not self._p1_tracked: | elif not self._p1_tracked: | ||||
| return AMBIGUOUS_TIME | return AMBIGUOUS_TIME | ||||
| else: | else: | ||||
| return self._mtime | return self._mtime_s | ||||
| def need_delay(self, now): | def need_delay(self, now): | ||||
| """True if the stored mtime would be ambiguous with the current time""" | """True if the stored mtime would be ambiguous with the current time""" | ||||
| return self.v1_state() == b'n' and self.v1_mtime() == now | return self.v1_state() == b'n' and self.mtime_likely_equal_to(now) | ||||
| def gettype(q): | def gettype(q): | ||||
| return int(q & 0xFFFF) | return int(q & 0xFFFF) | ||||
| class BaseIndexObject(object): | class BaseIndexObject(object): | ||||
| # Can I be passed to an algorithme implemented in Rust ? | # Can I be passed to an algorithme implemented in Rust ? | ||||
| if b'\0' in f: | if b'\0' in f: | ||||
| f, c = f.split(b'\0') | f, c = f.split(b'\0') | ||||
| copymap[f] = c | copymap[f] = c | ||||
| dmap[f] = DirstateItem.from_v1_data(*e[:4]) | dmap[f] = DirstateItem.from_v1_data(*e[:4]) | ||||
| return parents | return parents | ||||
| def pack_dirstate(dmap, copymap, pl, now): | def pack_dirstate(dmap, copymap, pl, now): | ||||
| now = int(now) | |||||
| cs = stringio() | cs = stringio() | ||||
| write = cs.write | write = cs.write | ||||
| write(b"".join(pl)) | write(b"".join(pl)) | ||||
| for f, e in pycompat.iteritems(dmap): | for f, e in pycompat.iteritems(dmap): | ||||
| if e.need_delay(now): | if e.need_delay(now): | ||||
| # The file was last modified "simultaneously" with the current | # The file was last modified "simultaneously" with the current | ||||
| # write to dirstate (i.e. within the same second for file- | # write to dirstate (i.e. within the same second for file- | ||||
| # systems with a granularity of 1 sec). This commonly happens | # systems with a granularity of 1 sec). This commonly happens | ||||
| use crate::dirstate_tree::on_disk::DirstateV2ParseError; | use crate::dirstate_tree::on_disk::DirstateV2ParseError; | ||||
| use crate::errors::HgError; | use crate::errors::HgError; | ||||
| use bitflags::bitflags; | use bitflags::bitflags; | ||||
| use std::convert::{TryFrom, TryInto}; | use std::convert::{TryFrom, TryInto}; | ||||
| use std::fs; | use std::fs; | ||||
| use std::io; | use std::io; | ||||
| use std::time::{SystemTime, UNIX_EPOCH}; | use std::time::{SystemTime, UNIX_EPOCH}; | ||||
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] | #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||||
| pub enum EntryState { | pub enum EntryState { | ||||
| Normal, | Normal, | ||||
| Added, | Added, | ||||
| Removed, | Removed, | ||||
| Merged, | Merged, | ||||
| } | } | ||||
| /// The C implementation uses all signed types. This will be an issue | /// `size` and `mtime.seconds` are truncated to 31 bits. | ||||
| /// either when 4GB+ source files are commonplace or in 2038, whichever | /// | ||||
| /// comes first. | /// TODO: double-check status algorithm correctness for files | ||||
| #[derive(Debug, PartialEq, Copy, Clone)] | /// larger than 2 GiB or modified after 2038. | ||||
| #[derive(Debug, Copy, Clone)] | |||||
| pub struct DirstateEntry { | pub struct DirstateEntry { | ||||
| pub(crate) flags: Flags, | pub(crate) flags: Flags, | ||||
| mode_size: Option<(u32, u32)>, | mode_size: Option<(u32, u32)>, | ||||
| mtime: Option<u32>, | mtime: Option<TruncatedTimestamp>, | ||||
| } | } | ||||
| bitflags! { | bitflags! { | ||||
| pub(crate) struct Flags: u8 { | pub(crate) struct Flags: u8 { | ||||
| const WDIR_TRACKED = 1 << 0; | const WDIR_TRACKED = 1 << 0; | ||||
| const P1_TRACKED = 1 << 1; | const P1_TRACKED = 1 << 1; | ||||
| const P2_INFO = 1 << 2; | const P2_INFO = 1 << 2; | ||||
| } | } | ||||
| } | } | ||||
| /// A Unix timestamp with nanoseconds precision | /// A Unix timestamp with nanoseconds precision | ||||
| #[derive(Copy, Clone)] | #[derive(Debug, Copy, Clone)] | ||||
| pub struct TruncatedTimestamp { | pub struct TruncatedTimestamp { | ||||
| truncated_seconds: u32, | truncated_seconds: u32, | ||||
| /// Always in the `0 .. 1_000_000_000` range. | /// Always in the `0 .. 1_000_000_000` range. | ||||
| nanoseconds: u32, | nanoseconds: u32, | ||||
| } | } | ||||
| impl TruncatedTimestamp { | impl TruncatedTimestamp { | ||||
| /// Constructs from a timestamp potentially outside of the supported range, | /// Constructs from a timestamp potentially outside of the supported range, | ||||
| pub const SIZE_NON_NORMAL: i32 = -1; | pub const SIZE_NON_NORMAL: i32 = -1; | ||||
| impl DirstateEntry { | impl DirstateEntry { | ||||
| pub fn from_v2_data( | pub fn from_v2_data( | ||||
| wdir_tracked: bool, | wdir_tracked: bool, | ||||
| p1_tracked: bool, | p1_tracked: bool, | ||||
| p2_info: bool, | p2_info: bool, | ||||
| mode_size: Option<(u32, u32)>, | mode_size: Option<(u32, u32)>, | ||||
| mtime: Option<u32>, | mtime: Option<TruncatedTimestamp>, | ||||
| ) -> Self { | ) -> Self { | ||||
| if let Some((mode, size)) = mode_size { | if let Some((mode, size)) = mode_size { | ||||
| // TODO: return an error for out of range values? | // TODO: return an error for out of range values? | ||||
| assert!(mode & !RANGE_MASK_31BIT == 0); | assert!(mode & !RANGE_MASK_31BIT == 0); | ||||
| assert!(size & !RANGE_MASK_31BIT == 0); | assert!(size & !RANGE_MASK_31BIT == 0); | ||||
| } | } | ||||
| if let Some(mtime) = mtime { | |||||
| assert!(mtime & !RANGE_MASK_31BIT == 0); | |||||
| } | |||||
| let mut flags = Flags::empty(); | let mut flags = Flags::empty(); | ||||
| flags.set(Flags::WDIR_TRACKED, wdir_tracked); | flags.set(Flags::WDIR_TRACKED, wdir_tracked); | ||||
| flags.set(Flags::P1_TRACKED, p1_tracked); | flags.set(Flags::P1_TRACKED, p1_tracked); | ||||
| flags.set(Flags::P2_INFO, p2_info); | flags.set(Flags::P2_INFO, p2_info); | ||||
| Self { | Self { | ||||
| flags, | flags, | ||||
| mode_size, | mode_size, | ||||
| mtime, | mtime, | ||||
| mode_size: Some((mode, size)), | mode_size: Some((mode, size)), | ||||
| mtime: None, | mtime: None, | ||||
| } | } | ||||
| } else { | } else { | ||||
| // TODO:!return an error for negative values? | // TODO:!return an error for negative values? | ||||
| let mode = u32::try_from(mode).unwrap(); | let mode = u32::try_from(mode).unwrap(); | ||||
| let size = u32::try_from(size).unwrap(); | let size = u32::try_from(size).unwrap(); | ||||
| let mtime = u32::try_from(mtime).unwrap(); | let mtime = u32::try_from(mtime).unwrap(); | ||||
| let mtime = | |||||
| TruncatedTimestamp::from_already_truncated(mtime, 0) | |||||
| .unwrap(); | |||||
| Self { | Self { | ||||
| flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, | ||||
| mode_size: Some((mode, size)), | mode_size: Some((mode, size)), | ||||
| mtime: Some(mtime), | mtime: Some(mtime), | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| EntryState::Added => Self { | EntryState::Added => Self { | ||||
| self.flags.intersects( | self.flags.intersects( | ||||
| Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, | Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, | ||||
| ) | ) | ||||
| } | } | ||||
| /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)` | /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)` | ||||
| pub(crate) fn v2_data( | pub(crate) fn v2_data( | ||||
| &self, | &self, | ||||
| ) -> (bool, bool, bool, Option<(u32, u32)>, Option<u32>) { | ) -> ( | ||||
| bool, | |||||
| bool, | |||||
| bool, | |||||
| Option<(u32, u32)>, | |||||
| Option<TruncatedTimestamp>, | |||||
| ) { | |||||
| if !self.any_tracked() { | if !self.any_tracked() { | ||||
| // TODO: return an Option instead? | // TODO: return an Option instead? | ||||
| panic!("Accessing v1_state of an untracked DirstateEntry") | panic!("Accessing v1_state of an untracked DirstateEntry") | ||||
| } | } | ||||
| let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); | let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); | ||||
| let p1_tracked = self.flags.contains(Flags::P1_TRACKED); | let p1_tracked = self.flags.contains(Flags::P1_TRACKED); | ||||
| let p2_info = self.flags.contains(Flags::P2_INFO); | let p2_info = self.flags.contains(Flags::P2_INFO); | ||||
| let mode_size = self.mode_size; | let mode_size = self.mode_size; | ||||
| } | } | ||||
| if self.removed() { | if self.removed() { | ||||
| 0 | 0 | ||||
| } else if self.flags.contains(Flags::P2_INFO) { | } else if self.flags.contains(Flags::P2_INFO) { | ||||
| MTIME_UNSET | MTIME_UNSET | ||||
| } else if !self.flags.contains(Flags::P1_TRACKED) { | } else if !self.flags.contains(Flags::P1_TRACKED) { | ||||
| MTIME_UNSET | MTIME_UNSET | ||||
| } else if let Some(mtime) = self.mtime { | } else if let Some(mtime) = self.mtime { | ||||
| i32::try_from(mtime).unwrap() | i32::try_from(mtime.truncated_seconds()).unwrap() | ||||
| } else { | } else { | ||||
| MTIME_UNSET | MTIME_UNSET | ||||
| } | } | ||||
| } | } | ||||
| // TODO: return `Option<EntryState>`? None when `!self.any_tracked` | // TODO: return `Option<EntryState>`? None when `!self.any_tracked` | ||||
| pub fn state(&self) -> EntryState { | pub fn state(&self) -> EntryState { | ||||
| self.v1_state() | self.v1_state() | ||||
| self.v1_size() | self.v1_size() | ||||
| } | } | ||||
| // TODO: return Option? | // TODO: return Option? | ||||
| pub fn mtime(&self) -> i32 { | pub fn mtime(&self) -> i32 { | ||||
| self.v1_mtime() | self.v1_mtime() | ||||
| } | } | ||||
| pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> { | |||||
| self.mtime | |||||
| } | |||||
| pub fn drop_merge_data(&mut self) { | pub fn drop_merge_data(&mut self) { | ||||
| if self.flags.contains(Flags::P2_INFO) { | if self.flags.contains(Flags::P2_INFO) { | ||||
| self.flags.remove(Flags::P2_INFO); | self.flags.remove(Flags::P2_INFO); | ||||
| self.mode_size = None; | self.mode_size = None; | ||||
| self.mtime = None; | self.mtime = None; | ||||
| } | } | ||||
| } | } | ||||
| pub fn set_possibly_dirty(&mut self) { | pub fn set_possibly_dirty(&mut self) { | ||||
| self.mtime = None | self.mtime = None | ||||
| } | } | ||||
| pub fn set_clean(&mut self, mode: u32, size: u32, mtime: u32) { | pub fn set_clean( | ||||
| &mut self, | |||||
| mode: u32, | |||||
| size: u32, | |||||
| mtime: TruncatedTimestamp, | |||||
| ) { | |||||
| let size = size & RANGE_MASK_31BIT; | let size = size & RANGE_MASK_31BIT; | ||||
| let mtime = mtime & RANGE_MASK_31BIT; | |||||
| self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); | self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED); | ||||
| self.mode_size = Some((mode, size)); | self.mode_size = Some((mode, size)); | ||||
| self.mtime = Some(mtime); | self.mtime = Some(mtime); | ||||
| } | } | ||||
| pub fn set_tracked(&mut self) { | pub fn set_tracked(&mut self) { | ||||
| self.flags.insert(Flags::WDIR_TRACKED); | self.flags.insert(Flags::WDIR_TRACKED); | ||||
| // `set_tracked` is replacing various `normallookup` call. So we mark | // `set_tracked` is replacing various `normallookup` call. So we mark | ||||
| } | } | ||||
| /// Returns a `(state, mode, size, mtime)` tuple as for | /// Returns a `(state, mode, size, mtime)` tuple as for | ||||
| /// `DirstateMapMethods::debug_iter`. | /// `DirstateMapMethods::debug_iter`. | ||||
| pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { | pub fn debug_tuple(&self) -> (u8, i32, i32, i32) { | ||||
| (self.state().into(), self.mode(), self.size(), self.mtime()) | (self.state().into(), self.mode(), self.size(), self.mtime()) | ||||
| } | } | ||||
| pub fn mtime_is_ambiguous(&self, now: i32) -> bool { | pub fn mtime_is_ambiguous(&self, now: TruncatedTimestamp) -> bool { | ||||
| self.state() == EntryState::Normal && self.mtime() == now | if let Some(mtime) = self.mtime { | ||||
| self.state() == EntryState::Normal && mtime.very_likely_equal(now) | |||||
| } else { | |||||
| false | |||||
| } | |||||
| } | } | ||||
| pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool { | pub fn clear_ambiguous_mtime(&mut self, now: TruncatedTimestamp) -> bool { | ||||
| let ambiguous = self.mtime_is_ambiguous(now); | let ambiguous = self.mtime_is_ambiguous(now); | ||||
| if ambiguous { | if ambiguous { | ||||
| // The file was last modified "simultaneously" with the current | // The file was last modified "simultaneously" with the current | ||||
| // write to dirstate (i.e. within the same second for file- | // write to dirstate (i.e. within the same second for file- | ||||
| // systems with a granularity of 1 sec). This commonly happens | // systems with a granularity of 1 sec). This commonly happens | ||||
| // for at least a couple of files on 'update'. | // for at least a couple of files on 'update'. | ||||
| // The user could change the file without changing its size | // The user could change the file without changing its size | ||||
| // within the same second. Invalidate the file's mtime in | // within the same second. Invalidate the file's mtime in | ||||
| packed.write_i32::<BigEndian>(mtime).unwrap(); | packed.write_i32::<BigEndian>(mtime).unwrap(); | ||||
| packed.write_i32::<BigEndian>(length as i32).unwrap(); | packed.write_i32::<BigEndian>(length as i32).unwrap(); | ||||
| packed.extend(filename.as_bytes()); | packed.extend(filename.as_bytes()); | ||||
| if let Some(source) = copy_source { | if let Some(source) = copy_source { | ||||
| packed.push(b'\0'); | packed.push(b'\0'); | ||||
| packed.extend(source.as_bytes()); | packed.extend(source.as_bytes()); | ||||
| } | } | ||||
| } | } | ||||
| /// Seconds since the Unix epoch | |||||
| pub struct Timestamp(pub i64); | |||||
| // status.rs | // status.rs | ||||
| // | // | ||||
| // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
| // | // | ||||
| // This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
| // GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
| //! Rust implementation of dirstate.status (dirstate.py). | //! Rust implementation of dirstate.status (dirstate.py). | ||||
| //! It is currently missing a lot of functionality compared to the Python one | //! It is currently missing a lot of functionality compared to the Python one | ||||
| //! and will only be triggered in narrow cases. | //! and will only be triggered in narrow cases. | ||||
| use crate::dirstate_tree::on_disk::DirstateV2ParseError; | use crate::dirstate_tree::on_disk::DirstateV2ParseError; | ||||
| use crate::{ | use crate::{ | ||||
| dirstate::TruncatedTimestamp, | |||||
| utils::hg_path::{HgPath, HgPathError}, | utils::hg_path::{HgPath, HgPathError}, | ||||
| PatternError, | PatternError, | ||||
| }; | }; | ||||
| use std::{borrow::Cow, fmt}; | use std::{borrow::Cow, fmt}; | ||||
| /// Wrong type of file from a `BadMatch` | /// Wrong type of file from a `BadMatch` | ||||
| /// Note: a lot of those don't exist on all platforms. | /// Note: a lot of those don't exist on all platforms. | ||||
| /// the dirstate/explicit) paths, this comes up a lot. | /// the dirstate/explicit) paths, this comes up a lot. | ||||
| pub type HgPathCow<'a> = Cow<'a, HgPath>; | pub type HgPathCow<'a> = Cow<'a, HgPath>; | ||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Copy, Clone)] | ||||
| pub struct StatusOptions { | pub struct StatusOptions { | ||||
| /// Remember the most recent modification timeslot for status, to make | /// Remember the most recent modification timeslot for status, to make | ||||
| /// sure we won't miss future size-preserving file content modifications | /// sure we won't miss future size-preserving file content modifications | ||||
| /// that happen within the same timeslot. | /// that happen within the same timeslot. | ||||
| pub last_normal_time: i64, | pub last_normal_time: TruncatedTimestamp, | ||||
| /// Whether we are on a filesystem with UNIX-like exec flags | /// Whether we are on a filesystem with UNIX-like exec flags | ||||
| pub check_exec: bool, | pub check_exec: bool, | ||||
| pub list_clean: bool, | pub list_clean: bool, | ||||
| pub list_unknown: bool, | pub list_unknown: bool, | ||||
| pub list_ignored: bool, | pub list_ignored: bool, | ||||
| /// Whether to collect traversed dirs for applying a callback later. | /// Whether to collect traversed dirs for applying a callback later. | ||||
| /// Used by `hg purge` for example. | /// Used by `hg purge` for example. | ||||
| pub collect_traversed_dirs: bool, | pub collect_traversed_dirs: bool, | ||||
| use bytes_cast::BytesCast; | use bytes_cast::BytesCast; | ||||
| use micro_timer::timed; | use micro_timer::timed; | ||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||
| use std::convert::TryInto; | |||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||
| use super::on_disk; | use super::on_disk; | ||||
| use super::on_disk::DirstateV2ParseError; | use super::on_disk::DirstateV2ParseError; | ||||
| use super::owning::OwningDirstateMap; | use super::owning::OwningDirstateMap; | ||||
| use super::path_with_basename::WithBasename; | use super::path_with_basename::WithBasename; | ||||
| use crate::dirstate::parsers::pack_entry; | use crate::dirstate::parsers::pack_entry; | ||||
| use crate::dirstate::parsers::packed_entry_size; | use crate::dirstate::parsers::packed_entry_size; | ||||
| use crate::dirstate::parsers::parse_dirstate_entries; | use crate::dirstate::parsers::parse_dirstate_entries; | ||||
| use crate::dirstate::parsers::Timestamp; | |||||
| use crate::dirstate::CopyMapIter; | use crate::dirstate::CopyMapIter; | ||||
| use crate::dirstate::StateMapIter; | use crate::dirstate::StateMapIter; | ||||
| use crate::dirstate::TruncatedTimestamp; | use crate::dirstate::TruncatedTimestamp; | ||||
| use crate::dirstate::SIZE_FROM_OTHER_PARENT; | use crate::dirstate::SIZE_FROM_OTHER_PARENT; | ||||
| use crate::dirstate::SIZE_NON_NORMAL; | use crate::dirstate::SIZE_NON_NORMAL; | ||||
| use crate::matchers::Matcher; | use crate::matchers::Matcher; | ||||
| use crate::utils::hg_path::{HgPath, HgPathBuf}; | use crate::utils::hg_path::{HgPath, HgPathBuf}; | ||||
| use crate::DirstateEntry; | use crate::DirstateEntry; | ||||
| Ok(false) | Ok(false) | ||||
| } | } | ||||
| } | } | ||||
| #[timed] | #[timed] | ||||
| pub fn pack_v1( | pub fn pack_v1( | ||||
| &mut self, | &mut self, | ||||
| parents: DirstateParents, | parents: DirstateParents, | ||||
| now: Timestamp, | now: TruncatedTimestamp, | ||||
| ) -> Result<Vec<u8>, DirstateError> { | ) -> Result<Vec<u8>, DirstateError> { | ||||
| let map = self.get_map_mut(); | let map = self.get_map_mut(); | ||||
| let now: i32 = now.0.try_into().expect("time overflow"); | |||||
| let mut ambiguous_mtimes = Vec::new(); | let mut ambiguous_mtimes = Vec::new(); | ||||
| // Optizimation (to be measured?): pre-compute size to avoid `Vec` | // Optizimation (to be measured?): pre-compute size to avoid `Vec` | ||||
| // reallocations | // reallocations | ||||
| let mut size = parents.as_bytes().len(); | let mut size = parents.as_bytes().len(); | ||||
| for node in map.iter_nodes() { | for node in map.iter_nodes() { | ||||
| let node = node?; | let node = node?; | ||||
| if let Some(entry) = node.entry()? { | if let Some(entry) = node.entry()? { | ||||
| size += packed_entry_size( | size += packed_entry_size( | ||||
| /// Returns new data and metadata together with whether that data should be | /// Returns new data and metadata together with whether that data should be | ||||
| /// appended to the existing data file whose content is at | /// appended to the existing data file whose content is at | ||||
| /// `map.on_disk` (true), instead of written to a new data file | /// `map.on_disk` (true), instead of written to a new data file | ||||
| /// (false). | /// (false). | ||||
| #[timed] | #[timed] | ||||
| pub fn pack_v2( | pub fn pack_v2( | ||||
| &mut self, | &mut self, | ||||
| now: Timestamp, | now: TruncatedTimestamp, | ||||
| can_append: bool, | can_append: bool, | ||||
| ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | ||||
| let map = self.get_map_mut(); | let map = self.get_map_mut(); | ||||
| // TODO:!how do we want to handle this in 2038? | |||||
| let now: i32 = now.0.try_into().expect("time overflow"); | |||||
| let mut paths = Vec::new(); | let mut paths = Vec::new(); | ||||
| for node in map.iter_nodes() { | for node in map.iter_nodes() { | ||||
| let node = node?; | let node = node?; | ||||
| if let Some(entry) = node.entry()? { | if let Some(entry) = node.entry()? { | ||||
| if entry.mtime_is_ambiguous(now) { | if entry.mtime_is_ambiguous(now) { | ||||
| paths.push( | paths.push( | ||||
| node.full_path_borrowed(map.on_disk)? | node.full_path_borrowed(map.on_disk)? | ||||
| .detach_from_tree(), | .detach_from_tree(), | ||||
| Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, | Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, | ||||
| ) | ) | ||||
| } | } | ||||
| pub(super) fn node_data( | pub(super) fn node_data( | ||||
| &self, | &self, | ||||
| ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> { | ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> { | ||||
| if self.has_entry() { | if self.has_entry() { | ||||
| Ok(dirstate_map::NodeData::Entry(self.assume_entry())) | Ok(dirstate_map::NodeData::Entry(self.assume_entry()?)) | ||||
| } else if let Some(mtime) = self.cached_directory_mtime()? { | } else if let Some(mtime) = self.cached_directory_mtime()? { | ||||
| Ok(dirstate_map::NodeData::CachedDirectory { mtime }) | Ok(dirstate_map::NodeData::CachedDirectory { mtime }) | ||||
| } else { | } else { | ||||
| Ok(dirstate_map::NodeData::None) | Ok(dirstate_map::NodeData::None) | ||||
| } | } | ||||
| } | } | ||||
| pub(super) fn cached_directory_mtime( | pub(super) fn cached_directory_mtime( | ||||
| let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) { | let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) { | ||||
| 0o755 | 0o755 | ||||
| } else { | } else { | ||||
| 0o644 | 0o644 | ||||
| }; | }; | ||||
| file_type | permisions | file_type | permisions | ||||
| } | } | ||||
| fn assume_entry(&self) -> DirstateEntry { | fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> { | ||||
| // TODO: convert through raw bits instead? | // TODO: convert through raw bits instead? | ||||
| let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); | let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); | ||||
| let p1_tracked = self.flags().contains(Flags::P1_TRACKED); | let p1_tracked = self.flags().contains(Flags::P1_TRACKED); | ||||
| let p2_info = self.flags().contains(Flags::P2_INFO); | let p2_info = self.flags().contains(Flags::P2_INFO); | ||||
| let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE) { | let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE) { | ||||
| Some((self.synthesize_unix_mode(), self.size.into())) | Some((self.synthesize_unix_mode(), self.size.into())) | ||||
| } else { | } else { | ||||
| None | None | ||||
| }; | }; | ||||
| let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME) { | let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME) { | ||||
| Some(self.mtime.truncated_seconds.into()) | Some(self.mtime.try_into()?) | ||||
| } else { | } else { | ||||
| None | None | ||||
| }; | }; | ||||
| DirstateEntry::from_v2_data( | Ok(DirstateEntry::from_v2_data( | ||||
| wdir_tracked, | wdir_tracked, | ||||
| p1_tracked, | p1_tracked, | ||||
| p2_info, | p2_info, | ||||
| mode_size, | mode_size, | ||||
| mtime, | mtime, | ||||
| ) | )) | ||||
| } | } | ||||
| pub(super) fn entry( | pub(super) fn entry( | ||||
| &self, | &self, | ||||
| ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> { | ||||
| if self.has_entry() { | if self.has_entry() { | ||||
| Ok(Some(self.assume_entry())) | Ok(Some(self.assume_entry()?)) | ||||
| } else { | } else { | ||||
| Ok(None) | Ok(None) | ||||
| } | } | ||||
| } | } | ||||
| pub(super) fn children<'on_disk>( | pub(super) fn children<'on_disk>( | ||||
| &self, | &self, | ||||
| on_disk: &'on_disk [u8], | on_disk: &'on_disk [u8], | ||||
| flags.set(Flags::MODE_IS_SYMLINK, is_symlink); | flags.set(Flags::MODE_IS_SYMLINK, is_symlink); | ||||
| flags.insert(Flags::HAS_MODE_AND_SIZE); | flags.insert(Flags::HAS_MODE_AND_SIZE); | ||||
| s.into() | s.into() | ||||
| } else { | } else { | ||||
| 0.into() | 0.into() | ||||
| }; | }; | ||||
| let mtime = if let Some(m) = mtime_opt { | let mtime = if let Some(m) = mtime_opt { | ||||
| flags.insert(Flags::HAS_FILE_MTIME); | flags.insert(Flags::HAS_FILE_MTIME); | ||||
| PackedTruncatedTimestamp { | m.into() | ||||
| truncated_seconds: m.into(), | |||||
| nanoseconds: 0.into(), | |||||
| } | |||||
| } else { | } else { | ||||
| PackedTruncatedTimestamp::null() | PackedTruncatedTimestamp::null() | ||||
| }; | }; | ||||
| (flags, size, mtime) | (flags, size, mtime) | ||||
| } | } | ||||
| } | } | ||||
| fn read_hg_path( | fn read_hg_path( | ||||
| self.outcome | self.outcome | ||||
| .lock() | .lock() | ||||
| .unwrap() | .unwrap() | ||||
| .modified | .modified | ||||
| .push(hg_path.detach_from_tree()) | .push(hg_path.detach_from_tree()) | ||||
| } else { | } else { | ||||
| let mtime = mtime_seconds(fs_metadata); | let mtime = mtime_seconds(fs_metadata); | ||||
| if truncate_i64(mtime) != entry.mtime() | if truncate_i64(mtime) != entry.mtime() | ||||
| || mtime == self.options.last_normal_time | || mtime | ||||
| == self.options.last_normal_time.truncated_seconds() as i64 | |||||
| { | { | ||||
| self.outcome | self.outcome | ||||
| .lock() | .lock() | ||||
| .unwrap() | .unwrap() | ||||
| .unsure | .unsure | ||||
| .push(hg_path.detach_from_tree()) | .push(hg_path.detach_from_tree()) | ||||
| } else if self.options.list_clean { | } else if self.options.list_clean { | ||||
| self.outcome | self.outcome | ||||
| py_fn!( | py_fn!( | ||||
| py, | py, | ||||
| status_wrapper( | status_wrapper( | ||||
| dmap: DirstateMap, | dmap: DirstateMap, | ||||
| root_dir: PyObject, | root_dir: PyObject, | ||||
| matcher: PyObject, | matcher: PyObject, | ||||
| ignorefiles: PyList, | ignorefiles: PyList, | ||||
| check_exec: bool, | check_exec: bool, | ||||
| last_normal_time: i64, | last_normal_time: (u32, u32), | ||||
| list_clean: bool, | list_clean: bool, | ||||
| list_ignored: bool, | list_ignored: bool, | ||||
| list_unknown: bool, | list_unknown: bool, | ||||
| collect_traversed_dirs: bool | collect_traversed_dirs: bool | ||||
| ) | ) | ||||
| ), | ), | ||||
| )?; | )?; | ||||
| let sys = PyModule::import(py, "sys")?; | let sys = PyModule::import(py, "sys")?; | ||||
| let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | ||||
| sys_modules.set_item(py, dotted_name, &m)?; | sys_modules.set_item(py, dotted_name, &m)?; | ||||
| Ok(m) | Ok(m) | ||||
| } | } | ||||
| use cpython::{ | use cpython::{ | ||||
| exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject, | exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject, | ||||
| PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked, | PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked, | ||||
| }; | }; | ||||
| use crate::{ | use crate::{ | ||||
| dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, | dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, | ||||
| dirstate::item::DirstateItem, | dirstate::item::{timestamp, DirstateItem}, | ||||
| pybytes_deref::PyBytesDeref, | pybytes_deref::PyBytesDeref, | ||||
| }; | }; | ||||
| use hg::{ | use hg::{ | ||||
| dirstate::parsers::Timestamp, | |||||
| dirstate::StateMapIter, | dirstate::StateMapIter, | ||||
| dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, | dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, | ||||
| dirstate_tree::on_disk::DirstateV2ParseError, | dirstate_tree::on_disk::DirstateV2ParseError, | ||||
| dirstate_tree::owning::OwningDirstateMap, | dirstate_tree::owning::OwningDirstateMap, | ||||
| revlog::Node, | revlog::Node, | ||||
| utils::files::normalize_case, | utils::files::normalize_case, | ||||
| utils::hg_path::{HgPath, HgPathBuf}, | utils::hg_path::{HgPath, HgPathBuf}, | ||||
| DirstateEntry, DirstateError, DirstateParents, EntryState, | DirstateEntry, DirstateError, DirstateParents, EntryState, | ||||
| })? | })? | ||||
| .to_py_object(py)) | .to_py_object(py)) | ||||
| } | } | ||||
| def write_v1( | def write_v1( | ||||
| &self, | &self, | ||||
| p1: PyObject, | p1: PyObject, | ||||
| p2: PyObject, | p2: PyObject, | ||||
| now: PyObject | now: (u32, u32) | ||||
| ) -> PyResult<PyBytes> { | ) -> PyResult<PyBytes> { | ||||
| let now = Timestamp(now.extract(py)?); | let now = timestamp(py, now)?; | ||||
| let mut inner = self.inner(py).borrow_mut(); | let mut inner = self.inner(py).borrow_mut(); | ||||
| let parents = DirstateParents { | let parents = DirstateParents { | ||||
| p1: extract_node_id(py, &p1)?, | p1: extract_node_id(py, &p1)?, | ||||
| p2: extract_node_id(py, &p2)?, | p2: extract_node_id(py, &p2)?, | ||||
| }; | }; | ||||
| let result = inner.pack_v1(parents, now); | let result = inner.pack_v1(parents, now); | ||||
| match result { | match result { | ||||
| Ok(packed) => Ok(PyBytes::new(py, &packed)), | Ok(packed) => Ok(PyBytes::new(py, &packed)), | ||||
| Err(_) => Err(PyErr::new::<exc::OSError, _>( | Err(_) => Err(PyErr::new::<exc::OSError, _>( | ||||
| py, | py, | ||||
| "Dirstate error".to_string(), | "Dirstate error".to_string(), | ||||
| )), | )), | ||||
| } | } | ||||
| } | } | ||||
| /// Returns new data together with whether that data should be appended to | /// Returns new data together with whether that data should be appended to | ||||
| /// the existing data file whose content is at `self.on_disk` (True), | /// the existing data file whose content is at `self.on_disk` (True), | ||||
| /// instead of written to a new data file (False). | /// instead of written to a new data file (False). | ||||
| def write_v2( | def write_v2( | ||||
| &self, | &self, | ||||
| now: PyObject, | now: (u32, u32), | ||||
| can_append: bool, | can_append: bool, | ||||
| ) -> PyResult<PyObject> { | ) -> PyResult<PyObject> { | ||||
| let now = Timestamp(now.extract(py)?); | let now = timestamp(py, now)?; | ||||
| let mut inner = self.inner(py).borrow_mut(); | let mut inner = self.inner(py).borrow_mut(); | ||||
| let result = inner.pack_v2(now, can_append); | let result = inner.pack_v2(now, can_append); | ||||
| match result { | match result { | ||||
| Ok((packed, tree_metadata, append)) => { | Ok((packed, tree_metadata, append)) => { | ||||
| let packed = PyBytes::new(py, &packed); | let packed = PyBytes::new(py, &packed); | ||||
| let tree_metadata = PyBytes::new(py, &tree_metadata); | let tree_metadata = PyBytes::new(py, &tree_metadata); | ||||
| let tuple = (packed, tree_metadata, append); | let tuple = (packed, tree_metadata, append); | ||||
| use cpython::exc; | use cpython::exc; | ||||
| use cpython::PyBytes; | use cpython::PyBytes; | ||||
| use cpython::PyErr; | use cpython::PyErr; | ||||
| use cpython::PyNone; | use cpython::PyNone; | ||||
| use cpython::PyObject; | use cpython::PyObject; | ||||
| use cpython::PyResult; | use cpython::PyResult; | ||||
| use cpython::Python; | use cpython::Python; | ||||
| use cpython::PythonObject; | use cpython::PythonObject; | ||||
| use hg::dirstate::DirstateEntry; | use hg::dirstate::DirstateEntry; | ||||
| use hg::dirstate::EntryState; | use hg::dirstate::EntryState; | ||||
| use hg::dirstate::TruncatedTimestamp; | |||||
| use std::cell::Cell; | use std::cell::Cell; | ||||
| use std::convert::TryFrom; | use std::convert::TryFrom; | ||||
| py_class!(pub class DirstateItem |py| { | py_class!(pub class DirstateItem |py| { | ||||
| data entry: Cell<DirstateEntry>; | data entry: Cell<DirstateEntry>; | ||||
| def __new__( | def __new__( | ||||
| _cls, | _cls, | ||||
| wc_tracked: bool = false, | wc_tracked: bool = false, | ||||
| p1_tracked: bool = false, | p1_tracked: bool = false, | ||||
| p2_info: bool = false, | p2_info: bool = false, | ||||
| has_meaningful_data: bool = true, | has_meaningful_data: bool = true, | ||||
| has_meaningful_mtime: bool = true, | has_meaningful_mtime: bool = true, | ||||
| parentfiledata: Option<(u32, u32, u32)> = None, | parentfiledata: Option<(u32, u32, (u32, u32))> = None, | ||||
| ) -> PyResult<DirstateItem> { | ) -> PyResult<DirstateItem> { | ||||
| let mut mode_size_opt = None; | let mut mode_size_opt = None; | ||||
| let mut mtime_opt = None; | let mut mtime_opt = None; | ||||
| if let Some((mode, size, mtime)) = parentfiledata { | if let Some((mode, size, mtime)) = parentfiledata { | ||||
| if has_meaningful_data { | if has_meaningful_data { | ||||
| mode_size_opt = Some((mode, size)) | mode_size_opt = Some((mode, size)) | ||||
| } | } | ||||
| if has_meaningful_mtime { | if has_meaningful_mtime { | ||||
| mtime_opt = Some(mtime) | mtime_opt = Some(timestamp(py, mtime)?) | ||||
| } | } | ||||
| } | } | ||||
| let entry = DirstateEntry::from_v2_data( | let entry = DirstateEntry::from_v2_data( | ||||
| wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt, | wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt, | ||||
| ); | ); | ||||
| DirstateItem::create_instance(py, Cell::new(entry)) | DirstateItem::create_instance(py, Cell::new(entry)) | ||||
| } | } | ||||
| Ok(size) | Ok(size) | ||||
| } | } | ||||
| def v1_mtime(&self) -> PyResult<i32> { | def v1_mtime(&self) -> PyResult<i32> { | ||||
| let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data(); | let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data(); | ||||
| Ok(mtime) | Ok(mtime) | ||||
| } | } | ||||
| def need_delay(&self, now: i32) -> PyResult<bool> { | def need_delay(&self, now: (u32, u32)) -> PyResult<bool> { | ||||
| let now = timestamp(py, now)?; | |||||
| Ok(self.entry(py).get().mtime_is_ambiguous(now)) | Ok(self.entry(py).get().mtime_is_ambiguous(now)) | ||||
| } | } | ||||
| def mtime_likely_equal_to(&self, other: (u32, u32)) -> PyResult<bool> { | |||||
| if let Some(mtime) = self.entry(py).get().truncated_mtime() { | |||||
| Ok(mtime.very_likely_equal(timestamp(py, other)?)) | |||||
| } else { | |||||
| Ok(false) | |||||
| } | |||||
| } | |||||
| @classmethod | @classmethod | ||||
| def from_v1_data( | def from_v1_data( | ||||
| _cls, | _cls, | ||||
| state: PyBytes, | state: PyBytes, | ||||
| mode: i32, | mode: i32, | ||||
| size: i32, | size: i32, | ||||
| mtime: i32, | mtime: i32, | ||||
| ) -> PyResult<Self> { | ) -> PyResult<Self> { | ||||
| self.update(py, |entry| entry.drop_merge_data()); | self.update(py, |entry| entry.drop_merge_data()); | ||||
| Ok(PyNone) | Ok(PyNone) | ||||
| } | } | ||||
| def set_clean( | def set_clean( | ||||
| &self, | &self, | ||||
| mode: u32, | mode: u32, | ||||
| size: u32, | size: u32, | ||||
| mtime: u32, | mtime: (u32, u32), | ||||
| ) -> PyResult<PyNone> { | ) -> PyResult<PyNone> { | ||||
| let mtime = timestamp(py, mtime)?; | |||||
| self.update(py, |entry| entry.set_clean(mode, size, mtime)); | self.update(py, |entry| entry.set_clean(mode, size, mtime)); | ||||
| Ok(PyNone) | Ok(PyNone) | ||||
| } | } | ||||
| def set_possibly_dirty(&self) -> PyResult<PyNone> { | def set_possibly_dirty(&self) -> PyResult<PyNone> { | ||||
| self.update(py, |entry| entry.set_possibly_dirty()); | self.update(py, |entry| entry.set_possibly_dirty()); | ||||
| Ok(PyNone) | Ok(PyNone) | ||||
| } | } | ||||
| // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable | // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable | ||||
| pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) { | pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) { | ||||
| let mut entry = self.entry(py).get(); | let mut entry = self.entry(py).get(); | ||||
| f(&mut entry); | f(&mut entry); | ||||
| self.entry(py).set(entry) | self.entry(py).set(entry) | ||||
| } | } | ||||
| } | } | ||||
| pub(crate) fn timestamp( | |||||
| py: Python<'_>, | |||||
| (s, ns): (u32, u32), | |||||
| ) -> PyResult<TruncatedTimestamp> { | |||||
| TruncatedTimestamp::from_already_truncated(s, ns).map_err(|_| { | |||||
| PyErr::new::<exc::ValueError, _>( | |||||
| py, | |||||
| "expected mtime truncated to 31 bits", | |||||
| ) | |||||
| }) | |||||
| } | |||||
| // status.rs | // status.rs | ||||
| // | // | ||||
| // Copyright 2019, Raphaël Gomès <rgomes@octobus.net> | // Copyright 2019, Raphaël Gomès <rgomes@octobus.net> | ||||
| // | // | ||||
| // This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
| // GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
| //! Bindings for the `hg::status` module provided by the | //! Bindings for the `hg::status` module provided by the | ||||
| //! `hg-core` crate. From Python, this will be seen as | //! `hg-core` crate. From Python, this will be seen as | ||||
| //! `rustext.dirstate.status`. | //! `rustext.dirstate.status`. | ||||
| use crate::dirstate::item::timestamp; | |||||
| use crate::{dirstate::DirstateMap, exceptions::FallbackError}; | use crate::{dirstate::DirstateMap, exceptions::FallbackError}; | ||||
| use cpython::exc::OSError; | use cpython::exc::OSError; | ||||
| use cpython::{ | use cpython::{ | ||||
| exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject, | exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject, | ||||
| PyResult, PyTuple, Python, PythonObject, ToPyObject, | PyResult, PyTuple, Python, PythonObject, ToPyObject, | ||||
| }; | }; | ||||
| use hg::{ | use hg::{ | ||||
| matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher}, | matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher}, | ||||
| pub fn status_wrapper( | pub fn status_wrapper( | ||||
| py: Python, | py: Python, | ||||
| dmap: DirstateMap, | dmap: DirstateMap, | ||||
| matcher: PyObject, | matcher: PyObject, | ||||
| root_dir: PyObject, | root_dir: PyObject, | ||||
| ignore_files: PyList, | ignore_files: PyList, | ||||
| check_exec: bool, | check_exec: bool, | ||||
| last_normal_time: i64, | last_normal_time: (u32, u32), | ||||
| list_clean: bool, | list_clean: bool, | ||||
| list_ignored: bool, | list_ignored: bool, | ||||
| list_unknown: bool, | list_unknown: bool, | ||||
| collect_traversed_dirs: bool, | collect_traversed_dirs: bool, | ||||
| ) -> PyResult<PyTuple> { | ) -> PyResult<PyTuple> { | ||||
| let last_normal_time = timestamp(py, last_normal_time)?; | |||||
| let bytes = root_dir.extract::<PyBytes>(py)?; | let bytes = root_dir.extract::<PyBytes>(py)?; | ||||
| let root_dir = get_path_from_bytes(bytes.data(py)); | let root_dir = get_path_from_bytes(bytes.data(py)); | ||||
| let dmap: DirstateMap = dmap.to_py_object(py); | let dmap: DirstateMap = dmap.to_py_object(py); | ||||
| let mut dmap = dmap.get_inner_mut(py); | let mut dmap = dmap.get_inner_mut(py); | ||||
| let ignore_files: PyResult<Vec<_>> = ignore_files | let ignore_files: PyResult<Vec<_>> = ignore_files | ||||
| .iter(py) | .iter(py) | ||||
| // status.rs | // status.rs | ||||
| // | // | ||||
| // Copyright 2020, Georges Racinet <georges.racinets@octobus.net> | // Copyright 2020, Georges Racinet <georges.racinets@octobus.net> | ||||
| // | // | ||||
| // This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
| // GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
| use crate::error::CommandError; | use crate::error::CommandError; | ||||
| use crate::ui::{Ui, UiError}; | use crate::ui::{Ui, UiError}; | ||||
| use crate::utils::path_utils::relativize_paths; | use crate::utils::path_utils::relativize_paths; | ||||
| use clap::{Arg, SubCommand}; | use clap::{Arg, SubCommand}; | ||||
| use hg; | use hg; | ||||
| use hg::config::Config; | use hg::config::Config; | ||||
| use hg::dirstate::TruncatedTimestamp; | |||||
| use hg::errors::HgError; | use hg::errors::HgError; | ||||
| use hg::manifest::Manifest; | use hg::manifest::Manifest; | ||||
| use hg::matchers::AlwaysMatcher; | use hg::matchers::AlwaysMatcher; | ||||
| use hg::repo::Repo; | use hg::repo::Repo; | ||||
| use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; | use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; | ||||
| use hg::{HgPathCow, StatusOptions}; | use hg::{HgPathCow, StatusOptions}; | ||||
| use log::{info, warn}; | use log::{info, warn}; | ||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||
| let repo = invocation.repo?; | let repo = invocation.repo?; | ||||
| let mut dmap = repo.dirstate_map_mut()?; | let mut dmap = repo.dirstate_map_mut()?; | ||||
| let options = StatusOptions { | let options = StatusOptions { | ||||
| // TODO should be provided by the dirstate parsing and | // TODO should be provided by the dirstate parsing and | ||||
| // hence be stored on dmap. Using a value that assumes we aren't | // hence be stored on dmap. Using a value that assumes we aren't | ||||
| // below the time resolution granularity of the FS and the | // below the time resolution granularity of the FS and the | ||||
| // dirstate. | // dirstate. | ||||
| last_normal_time: 0, | last_normal_time: TruncatedTimestamp::new_truncate(0, 0), | ||||
| // we're currently supporting file systems with exec flags only | // we're currently supporting file systems with exec flags only | ||||
| // anyway | // anyway | ||||
| check_exec: true, | check_exec: true, | ||||
| list_clean: display_states.clean, | list_clean: display_states.clean, | ||||
| list_unknown: display_states.unknown, | list_unknown: display_states.unknown, | ||||
| list_ignored: display_states.ignored, | list_ignored: display_states.ignored, | ||||
| collect_traversed_dirs: false, | collect_traversed_dirs: false, | ||||
| }; | }; | ||||
| from mercurial import ( | from mercurial import ( | ||||
| context, | context, | ||||
| dirstate, | dirstate, | ||||
| dirstatemap as dirstatemapmod, | dirstatemap as dirstatemapmod, | ||||
| extensions, | extensions, | ||||
| policy, | policy, | ||||
| registrar, | registrar, | ||||
| ) | ) | ||||
| from mercurial.dirstateutils import timestamp | |||||
| from mercurial.utils import dateutil | from mercurial.utils import dateutil | ||||
| try: | try: | ||||
| from mercurial import rustext | from mercurial import rustext | ||||
| rustext.__name__ # force actual import (see hgdemandimport) | rustext.__name__ # force actual import (see hgdemandimport) | ||||
| except ImportError: | except ImportError: | ||||
| rustext = None | rustext = None | ||||
| parsers = policy.importmod('parsers') | parsers = policy.importmod('parsers') | ||||
| has_rust_dirstate = policy.importrust('dirstate') is not None | has_rust_dirstate = policy.importrust('dirstate') is not None | ||||
| def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): | def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): | ||||
| # execute what original parsers.pack_dirstate should do actually | # execute what original parsers.pack_dirstate should do actually | ||||
| # for consistency | # for consistency | ||||
| actualnow = int(now) | |||||
| for f, e in dmap.items(): | for f, e in dmap.items(): | ||||
| if e.need_delay(actualnow): | if e.need_delay(now): | ||||
| e.set_possibly_dirty() | e.set_possibly_dirty() | ||||
| return orig(dmap, copymap, pl, fakenow) | return orig(dmap, copymap, pl, fakenow) | ||||
| def fakewrite(ui, func): | def fakewrite(ui, func): | ||||
| # fake "now" of 'pack_dirstate' only if it is invoked while 'func' | # fake "now" of 'pack_dirstate' only if it is invoked while 'func' | ||||
| fakenow = ui.config(b'fakedirstatewritetime', b'fakenow') | fakenow = ui.config(b'fakedirstatewritetime', b'fakenow') | ||||
| if not fakenow: | if not fakenow: | ||||
| # Execute original one, if fakenow isn't configured. This is | # Execute original one, if fakenow isn't configured. This is | ||||
| # useful to prevent subrepos from executing replaced one, | # useful to prevent subrepos from executing replaced one, | ||||
| # because replacing 'parsers.pack_dirstate' is also effective | # because replacing 'parsers.pack_dirstate' is also effective | ||||
| # in subrepos. | # in subrepos. | ||||
| return func() | return func() | ||||
| # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between | # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between | ||||
| # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy | # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy | ||||
| fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] | fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] | ||||
| fakenow = timestamp.timestamp((fakenow, 0)) | |||||
| if has_rust_dirstate: | if has_rust_dirstate: | ||||
| # The Rust implementation does not use public parse/pack dirstate | # The Rust implementation does not use public parse/pack dirstate | ||||
| # to prevent conversion round-trips | # to prevent conversion round-trips | ||||
| orig_dirstatemap_write = dirstatemapmod.dirstatemap.write | orig_dirstatemap_write = dirstatemapmod.dirstatemap.write | ||||
| wrapper = lambda self, tr, st, now: orig_dirstatemap_write( | wrapper = lambda self, tr, st, now: orig_dirstatemap_write( | ||||
| self, tr, st, fakenow | self, tr, st, fakenow | ||||
| ) | ) | ||||
Note that this comment still refers to "normal" and that lastnormaltime is still used. This could be worth following-up with a more up-to-date semantic.