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 (36 lines) | |||
M | mercurial/dirstatemap.py (1 line) | |||
A | M | mercurial/dirstateutils/timestamp.py (53 lines) | ||
M | mercurial/dirstateutils/v2.py (9 lines) | |||
M | mercurial/merge.py (7 lines) | |||
M | mercurial/pure/parsers.py (49 lines) | |||
M | rust/hg-core/src/dirstate/entry.rs (58 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 (25 lines) | |||
M | rust/hg-core/src/dirstate_tree/status.rs (29 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 |
---|---|---|---|---|
b425f033dc42 | 65ae59182fb9 | 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)) |
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) | # Don’t yet use sub-second precision if it exists in the file, | ||||
# since other parts of the code still set it to zero. | |||||
mtime_ns = 0 | |||||
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, | ||||
Ok(Self::new_truncate(seconds, nanoseconds)) | Ok(Self::new_truncate(seconds, nanoseconds)) | ||||
} | } | ||||
#[cfg(not(unix))] | #[cfg(not(unix))] | ||||
{ | { | ||||
metadata.modified().map(Self::from) | metadata.modified().map(Self::from) | ||||
} | } | ||||
} | } | ||||
pub fn to_integer_second(mut self) -> Self { | |||||
self.nanoseconds = 0; | |||||
self | |||||
} | |||||
/// The lower 31 bits of the number of seconds since the epoch. | /// The lower 31 bits of the number of seconds since the epoch. | ||||
pub fn truncated_seconds(&self) -> u32 { | pub fn truncated_seconds(&self) -> u32 { | ||||
self.truncated_seconds | self.truncated_seconds | ||||
} | } | ||||
/// The sub-second component of this timestamp, in nanoseconds. | /// The sub-second component of this timestamp, in nanoseconds. | ||||
/// Always in the `0 .. 1_000_000_000` range. | /// Always in the `0 .. 1_000_000_000` 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.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()) | // TODO: replace this by `self.mtime.try_into()?` to use | ||||
// sub-second precision from the file. | |||||
// We don’t do this yet because other parts of the code | |||||
// always set it to zero. | |||||
let mtime = TruncatedTimestamp::from_already_truncated( | |||||
self.mtime.truncated_seconds.get(), | |||||
0, | |||||
)?; | |||||
Some(mtime) | |||||
} 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, | &self, | ||||
dirstate_node: &NodeRef<'tree, 'on_disk>, | dirstate_node: &NodeRef<'tree, 'on_disk>, | ||||
fs_metadata: &std::fs::Metadata, | fs_metadata: &std::fs::Metadata, | ||||
) -> Result<(), DirstateV2ParseError> { | ) -> Result<(), DirstateV2ParseError> { | ||||
// Keep the low 31 bits | // Keep the low 31 bits | ||||
fn truncate_u64(value: u64) -> i32 { | fn truncate_u64(value: u64) -> i32 { | ||||
(value & 0x7FFF_FFFF) as i32 | (value & 0x7FFF_FFFF) as i32 | ||||
} | } | ||||
fn truncate_i64(value: i64) -> i32 { | |||||
(value & 0x7FFF_FFFF) as i32 | |||||
} | |||||
let entry = dirstate_node | let entry = dirstate_node | ||||
.entry()? | .entry()? | ||||
.expect("handle_normal_file called with entry-less node"); | .expect("handle_normal_file called with entry-less node"); | ||||
let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; | let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; | ||||
let mode_changed = | let mode_changed = | ||||
|| self.options.check_exec && entry.mode_changed(fs_metadata); | || self.options.check_exec && entry.mode_changed(fs_metadata); | ||||
let size = entry.size(); | let size = entry.size(); | ||||
|| (size >= 0 && (size_changed || mode_changed())) | || (size >= 0 && (size_changed || mode_changed())) | ||||
{ | { | ||||
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_looks_clean; | ||||
if truncate_i64(mtime) != entry.mtime() | if let Some(dirstate_mtime) = entry.truncated_mtime() { | ||||
|| mtime == self.options.last_normal_time | let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata) | ||||
{ | .expect("OS/libc does not support mtime?") | ||||
// For now don’t use sub-second precision for file mtimes | |||||
.to_integer_second(); | |||||
mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime) | |||||
&& !fs_mtime.likely_equal(self.options.last_normal_time) | |||||
} else { | |||||
// No mtime in the dirstate entry | |||||
mtime_looks_clean = false | |||||
}; | |||||
if !mtime_looks_clean { | |||||
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 | ||||
.lock() | .lock() | ||||
.unknown | .unknown | ||||
.push(hg_path.detach_from_tree()) | .push(hg_path.detach_from_tree()) | ||||
} | } | ||||
} | } | ||||
is_ignored | is_ignored | ||||
} | } | ||||
} | } | ||||
#[cfg(unix)] // TODO | |||||
fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 { | |||||
// Going through `Metadata::modified()` would be portable, but would take | |||||
// care to construct a `SystemTime` value with sub-second precision just | |||||
// for us to throw that away here. | |||||
use std::os::unix::fs::MetadataExt; | |||||
metadata.mtime() | |||||
} | |||||
struct DirEntry { | struct DirEntry { | ||||
base_name: HgPathBuf, | base_name: HgPathBuf, | ||||
full_path: PathBuf, | full_path: PathBuf, | ||||
metadata: std::fs::Metadata, | metadata: std::fs::Metadata, | ||||
} | } | ||||
impl DirEntry { | impl DirEntry { | ||||
/// Returns **unsorted** entries in the given directory, with name and | /// Returns **unsorted** entries in the given directory, with name and |
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.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.