##// END OF EJS Templates
dirstate-entry: add a `removed` property...
marmoute -
r48304:c94d3ff4 default
parent child Browse files
Show More
@@ -1,827 +1,837
1 /*
1 /*
2 parsers.c - efficient content parsing
2 parsers.c - efficient content parsing
3
3
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #define PY_SSIZE_T_CLEAN
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
11 #include <Python.h>
12 #include <ctype.h>
12 #include <ctype.h>
13 #include <stddef.h>
13 #include <stddef.h>
14 #include <string.h>
14 #include <string.h>
15
15
16 #include "bitmanipulation.h"
16 #include "bitmanipulation.h"
17 #include "charencode.h"
17 #include "charencode.h"
18 #include "util.h"
18 #include "util.h"
19
19
20 #ifdef IS_PY3K
20 #ifdef IS_PY3K
21 /* The mapping of Python types is meant to be temporary to get Python
21 /* The mapping of Python types is meant to be temporary to get Python
22 * 3 to compile. We should remove this once Python 3 support is fully
22 * 3 to compile. We should remove this once Python 3 support is fully
23 * supported and proper types are used in the extensions themselves. */
23 * supported and proper types are used in the extensions themselves. */
24 #define PyInt_Check PyLong_Check
24 #define PyInt_Check PyLong_Check
25 #define PyInt_FromLong PyLong_FromLong
25 #define PyInt_FromLong PyLong_FromLong
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 #define PyInt_AsLong PyLong_AsLong
27 #define PyInt_AsLong PyLong_AsLong
28 #endif
28 #endif
29
29
30 static const char *const versionerrortext = "Python minor version mismatch";
30 static const char *const versionerrortext = "Python minor version mismatch";
31
31
32 static const int dirstate_v1_from_p2 = -2;
32 static const int dirstate_v1_from_p2 = -2;
33
33
34 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
34 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
35 {
35 {
36 Py_ssize_t expected_size;
36 Py_ssize_t expected_size;
37
37
38 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
38 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
39 return NULL;
39 return NULL;
40 }
40 }
41
41
42 return _dict_new_presized(expected_size);
42 return _dict_new_presized(expected_size);
43 }
43 }
44
44
45 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
45 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
46 int size, int mtime)
46 int size, int mtime)
47 {
47 {
48 dirstateTupleObject *t =
48 dirstateTupleObject *t =
49 PyObject_New(dirstateTupleObject, &dirstateTupleType);
49 PyObject_New(dirstateTupleObject, &dirstateTupleType);
50 if (!t) {
50 if (!t) {
51 return NULL;
51 return NULL;
52 }
52 }
53 t->state = state;
53 t->state = state;
54 t->mode = mode;
54 t->mode = mode;
55 t->size = size;
55 t->size = size;
56 t->mtime = mtime;
56 t->mtime = mtime;
57 return t;
57 return t;
58 }
58 }
59
59
60 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
60 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
61 PyObject *kwds)
61 PyObject *kwds)
62 {
62 {
63 /* We do all the initialization here and not a tp_init function because
63 /* We do all the initialization here and not a tp_init function because
64 * dirstate_tuple is immutable. */
64 * dirstate_tuple is immutable. */
65 dirstateTupleObject *t;
65 dirstateTupleObject *t;
66 char state;
66 char state;
67 int size, mode, mtime;
67 int size, mode, mtime;
68 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
68 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
69 return NULL;
69 return NULL;
70 }
70 }
71
71
72 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
72 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
73 if (!t) {
73 if (!t) {
74 return NULL;
74 return NULL;
75 }
75 }
76 t->state = state;
76 t->state = state;
77 t->mode = mode;
77 t->mode = mode;
78 t->size = size;
78 t->size = size;
79 t->mtime = mtime;
79 t->mtime = mtime;
80
80
81 return (PyObject *)t;
81 return (PyObject *)t;
82 }
82 }
83
83
84 static void dirstate_tuple_dealloc(PyObject *o)
84 static void dirstate_tuple_dealloc(PyObject *o)
85 {
85 {
86 PyObject_Del(o);
86 PyObject_Del(o);
87 }
87 }
88
88
89 static Py_ssize_t dirstate_tuple_length(PyObject *o)
89 static Py_ssize_t dirstate_tuple_length(PyObject *o)
90 {
90 {
91 return 4;
91 return 4;
92 }
92 }
93
93
94 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
94 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
95 {
95 {
96 dirstateTupleObject *t = (dirstateTupleObject *)o;
96 dirstateTupleObject *t = (dirstateTupleObject *)o;
97 switch (i) {
97 switch (i) {
98 case 0:
98 case 0:
99 return PyBytes_FromStringAndSize(&t->state, 1);
99 return PyBytes_FromStringAndSize(&t->state, 1);
100 case 1:
100 case 1:
101 return PyInt_FromLong(t->mode);
101 return PyInt_FromLong(t->mode);
102 case 2:
102 case 2:
103 return PyInt_FromLong(t->size);
103 return PyInt_FromLong(t->size);
104 case 3:
104 case 3:
105 return PyInt_FromLong(t->mtime);
105 return PyInt_FromLong(t->mtime);
106 default:
106 default:
107 PyErr_SetString(PyExc_IndexError, "index out of range");
107 PyErr_SetString(PyExc_IndexError, "index out of range");
108 return NULL;
108 return NULL;
109 }
109 }
110 }
110 }
111
111
112 static PySequenceMethods dirstate_tuple_sq = {
112 static PySequenceMethods dirstate_tuple_sq = {
113 dirstate_tuple_length, /* sq_length */
113 dirstate_tuple_length, /* sq_length */
114 0, /* sq_concat */
114 0, /* sq_concat */
115 0, /* sq_repeat */
115 0, /* sq_repeat */
116 dirstate_tuple_item, /* sq_item */
116 dirstate_tuple_item, /* sq_item */
117 0, /* sq_ass_item */
117 0, /* sq_ass_item */
118 0, /* sq_contains */
118 0, /* sq_contains */
119 0, /* sq_inplace_concat */
119 0, /* sq_inplace_concat */
120 0 /* sq_inplace_repeat */
120 0 /* sq_inplace_repeat */
121 };
121 };
122
122
123 static PyObject *dirstatetuple_v1_state(dirstateTupleObject *self)
123 static PyObject *dirstatetuple_v1_state(dirstateTupleObject *self)
124 {
124 {
125 return PyBytes_FromStringAndSize(&self->state, 1);
125 return PyBytes_FromStringAndSize(&self->state, 1);
126 };
126 };
127
127
128 static PyObject *dirstatetuple_v1_mode(dirstateTupleObject *self)
128 static PyObject *dirstatetuple_v1_mode(dirstateTupleObject *self)
129 {
129 {
130 return PyInt_FromLong(self->mode);
130 return PyInt_FromLong(self->mode);
131 };
131 };
132
132
133 static PyObject *dirstatetuple_v1_size(dirstateTupleObject *self)
133 static PyObject *dirstatetuple_v1_size(dirstateTupleObject *self)
134 {
134 {
135 return PyInt_FromLong(self->size);
135 return PyInt_FromLong(self->size);
136 };
136 };
137
137
138 static PyObject *dirstatetuple_v1_mtime(dirstateTupleObject *self)
138 static PyObject *dirstatetuple_v1_mtime(dirstateTupleObject *self)
139 {
139 {
140 return PyInt_FromLong(self->mtime);
140 return PyInt_FromLong(self->mtime);
141 };
141 };
142
142
143 static PyMethodDef dirstatetuple_methods[] = {
143 static PyMethodDef dirstatetuple_methods[] = {
144 {"v1_state", (PyCFunction)dirstatetuple_v1_state, METH_NOARGS,
144 {"v1_state", (PyCFunction)dirstatetuple_v1_state, METH_NOARGS,
145 "return a \"state\" suitable for v1 serialization"},
145 "return a \"state\" suitable for v1 serialization"},
146 {"v1_mode", (PyCFunction)dirstatetuple_v1_mode, METH_NOARGS,
146 {"v1_mode", (PyCFunction)dirstatetuple_v1_mode, METH_NOARGS,
147 "return a \"mode\" suitable for v1 serialization"},
147 "return a \"mode\" suitable for v1 serialization"},
148 {"v1_size", (PyCFunction)dirstatetuple_v1_size, METH_NOARGS,
148 {"v1_size", (PyCFunction)dirstatetuple_v1_size, METH_NOARGS,
149 "return a \"size\" suitable for v1 serialization"},
149 "return a \"size\" suitable for v1 serialization"},
150 {"v1_mtime", (PyCFunction)dirstatetuple_v1_mtime, METH_NOARGS,
150 {"v1_mtime", (PyCFunction)dirstatetuple_v1_mtime, METH_NOARGS,
151 "return a \"mtime\" suitable for v1 serialization"},
151 "return a \"mtime\" suitable for v1 serialization"},
152 {NULL} /* Sentinel */
152 {NULL} /* Sentinel */
153 };
153 };
154
154
155 static PyObject *dirstatetuple_get_state(dirstateTupleObject *self)
155 static PyObject *dirstatetuple_get_state(dirstateTupleObject *self)
156 {
156 {
157 return PyBytes_FromStringAndSize(&self->state, 1);
157 return PyBytes_FromStringAndSize(&self->state, 1);
158 };
158 };
159
159
160 static PyObject *dirstatetuple_get_merged(dirstateTupleObject *self)
160 static PyObject *dirstatetuple_get_merged(dirstateTupleObject *self)
161 {
161 {
162 if (self->state == 'm') {
162 if (self->state == 'm') {
163 Py_RETURN_TRUE;
163 Py_RETURN_TRUE;
164 } else {
164 } else {
165 Py_RETURN_FALSE;
165 Py_RETURN_FALSE;
166 }
166 }
167 };
167 };
168
168
169 static PyObject *dirstatetuple_get_from_p2(dirstateTupleObject *self)
169 static PyObject *dirstatetuple_get_from_p2(dirstateTupleObject *self)
170 {
170 {
171 if (self->size == dirstate_v1_from_p2) {
171 if (self->size == dirstate_v1_from_p2) {
172 Py_RETURN_TRUE;
172 Py_RETURN_TRUE;
173 } else {
173 } else {
174 Py_RETURN_FALSE;
174 Py_RETURN_FALSE;
175 }
175 }
176 };
176 };
177
177
178 static PyObject *dirstatetuple_get_removed(dirstateTupleObject *self)
179 {
180 if (self->state == 'r') {
181 Py_RETURN_TRUE;
182 } else {
183 Py_RETURN_FALSE;
184 }
185 };
186
178 static PyGetSetDef dirstatetuple_getset[] = {
187 static PyGetSetDef dirstatetuple_getset[] = {
179 {"state", (getter)dirstatetuple_get_state, NULL, "state", NULL},
188 {"state", (getter)dirstatetuple_get_state, NULL, "state", NULL},
180 {"merged", (getter)dirstatetuple_get_merged, NULL, "merged", NULL},
189 {"merged", (getter)dirstatetuple_get_merged, NULL, "merged", NULL},
181 {"from_p2", (getter)dirstatetuple_get_from_p2, NULL, "from_p2", NULL},
190 {"from_p2", (getter)dirstatetuple_get_from_p2, NULL, "from_p2", NULL},
191 {"removed", (getter)dirstatetuple_get_removed, NULL, "removed", NULL},
182 {NULL} /* Sentinel */
192 {NULL} /* Sentinel */
183 };
193 };
184
194
185 PyTypeObject dirstateTupleType = {
195 PyTypeObject dirstateTupleType = {
186 PyVarObject_HEAD_INIT(NULL, 0) /* header */
196 PyVarObject_HEAD_INIT(NULL, 0) /* header */
187 "dirstate_tuple", /* tp_name */
197 "dirstate_tuple", /* tp_name */
188 sizeof(dirstateTupleObject), /* tp_basicsize */
198 sizeof(dirstateTupleObject), /* tp_basicsize */
189 0, /* tp_itemsize */
199 0, /* tp_itemsize */
190 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
200 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
191 0, /* tp_print */
201 0, /* tp_print */
192 0, /* tp_getattr */
202 0, /* tp_getattr */
193 0, /* tp_setattr */
203 0, /* tp_setattr */
194 0, /* tp_compare */
204 0, /* tp_compare */
195 0, /* tp_repr */
205 0, /* tp_repr */
196 0, /* tp_as_number */
206 0, /* tp_as_number */
197 &dirstate_tuple_sq, /* tp_as_sequence */
207 &dirstate_tuple_sq, /* tp_as_sequence */
198 0, /* tp_as_mapping */
208 0, /* tp_as_mapping */
199 0, /* tp_hash */
209 0, /* tp_hash */
200 0, /* tp_call */
210 0, /* tp_call */
201 0, /* tp_str */
211 0, /* tp_str */
202 0, /* tp_getattro */
212 0, /* tp_getattro */
203 0, /* tp_setattro */
213 0, /* tp_setattro */
204 0, /* tp_as_buffer */
214 0, /* tp_as_buffer */
205 Py_TPFLAGS_DEFAULT, /* tp_flags */
215 Py_TPFLAGS_DEFAULT, /* tp_flags */
206 "dirstate tuple", /* tp_doc */
216 "dirstate tuple", /* tp_doc */
207 0, /* tp_traverse */
217 0, /* tp_traverse */
208 0, /* tp_clear */
218 0, /* tp_clear */
209 0, /* tp_richcompare */
219 0, /* tp_richcompare */
210 0, /* tp_weaklistoffset */
220 0, /* tp_weaklistoffset */
211 0, /* tp_iter */
221 0, /* tp_iter */
212 0, /* tp_iternext */
222 0, /* tp_iternext */
213 dirstatetuple_methods, /* tp_methods */
223 dirstatetuple_methods, /* tp_methods */
214 0, /* tp_members */
224 0, /* tp_members */
215 dirstatetuple_getset, /* tp_getset */
225 dirstatetuple_getset, /* tp_getset */
216 0, /* tp_base */
226 0, /* tp_base */
217 0, /* tp_dict */
227 0, /* tp_dict */
218 0, /* tp_descr_get */
228 0, /* tp_descr_get */
219 0, /* tp_descr_set */
229 0, /* tp_descr_set */
220 0, /* tp_dictoffset */
230 0, /* tp_dictoffset */
221 0, /* tp_init */
231 0, /* tp_init */
222 0, /* tp_alloc */
232 0, /* tp_alloc */
223 dirstate_tuple_new, /* tp_new */
233 dirstate_tuple_new, /* tp_new */
224 };
234 };
225
235
226 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
236 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
227 {
237 {
228 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
238 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
229 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
239 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
230 char state, *cur, *str, *cpos;
240 char state, *cur, *str, *cpos;
231 int mode, size, mtime;
241 int mode, size, mtime;
232 unsigned int flen, pos = 40;
242 unsigned int flen, pos = 40;
233 Py_ssize_t len = 40;
243 Py_ssize_t len = 40;
234 Py_ssize_t readlen;
244 Py_ssize_t readlen;
235
245
236 if (!PyArg_ParseTuple(
246 if (!PyArg_ParseTuple(
237 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
247 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
238 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
248 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
239 goto quit;
249 goto quit;
240 }
250 }
241
251
242 len = readlen;
252 len = readlen;
243
253
244 /* read parents */
254 /* read parents */
245 if (len < 40) {
255 if (len < 40) {
246 PyErr_SetString(PyExc_ValueError,
256 PyErr_SetString(PyExc_ValueError,
247 "too little data for parents");
257 "too little data for parents");
248 goto quit;
258 goto quit;
249 }
259 }
250
260
251 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
261 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
252 str + 20, (Py_ssize_t)20);
262 str + 20, (Py_ssize_t)20);
253 if (!parents) {
263 if (!parents) {
254 goto quit;
264 goto quit;
255 }
265 }
256
266
257 /* read filenames */
267 /* read filenames */
258 while (pos >= 40 && pos < len) {
268 while (pos >= 40 && pos < len) {
259 if (pos + 17 > len) {
269 if (pos + 17 > len) {
260 PyErr_SetString(PyExc_ValueError,
270 PyErr_SetString(PyExc_ValueError,
261 "overflow in dirstate");
271 "overflow in dirstate");
262 goto quit;
272 goto quit;
263 }
273 }
264 cur = str + pos;
274 cur = str + pos;
265 /* unpack header */
275 /* unpack header */
266 state = *cur;
276 state = *cur;
267 mode = getbe32(cur + 1);
277 mode = getbe32(cur + 1);
268 size = getbe32(cur + 5);
278 size = getbe32(cur + 5);
269 mtime = getbe32(cur + 9);
279 mtime = getbe32(cur + 9);
270 flen = getbe32(cur + 13);
280 flen = getbe32(cur + 13);
271 pos += 17;
281 pos += 17;
272 cur += 17;
282 cur += 17;
273 if (flen > len - pos) {
283 if (flen > len - pos) {
274 PyErr_SetString(PyExc_ValueError,
284 PyErr_SetString(PyExc_ValueError,
275 "overflow in dirstate");
285 "overflow in dirstate");
276 goto quit;
286 goto quit;
277 }
287 }
278
288
279 entry =
289 entry =
280 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
290 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
281 cpos = memchr(cur, 0, flen);
291 cpos = memchr(cur, 0, flen);
282 if (cpos) {
292 if (cpos) {
283 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
293 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
284 cname = PyBytes_FromStringAndSize(
294 cname = PyBytes_FromStringAndSize(
285 cpos + 1, flen - (cpos - cur) - 1);
295 cpos + 1, flen - (cpos - cur) - 1);
286 if (!fname || !cname ||
296 if (!fname || !cname ||
287 PyDict_SetItem(cmap, fname, cname) == -1 ||
297 PyDict_SetItem(cmap, fname, cname) == -1 ||
288 PyDict_SetItem(dmap, fname, entry) == -1) {
298 PyDict_SetItem(dmap, fname, entry) == -1) {
289 goto quit;
299 goto quit;
290 }
300 }
291 Py_DECREF(cname);
301 Py_DECREF(cname);
292 } else {
302 } else {
293 fname = PyBytes_FromStringAndSize(cur, flen);
303 fname = PyBytes_FromStringAndSize(cur, flen);
294 if (!fname ||
304 if (!fname ||
295 PyDict_SetItem(dmap, fname, entry) == -1) {
305 PyDict_SetItem(dmap, fname, entry) == -1) {
296 goto quit;
306 goto quit;
297 }
307 }
298 }
308 }
299 Py_DECREF(fname);
309 Py_DECREF(fname);
300 Py_DECREF(entry);
310 Py_DECREF(entry);
301 fname = cname = entry = NULL;
311 fname = cname = entry = NULL;
302 pos += flen;
312 pos += flen;
303 }
313 }
304
314
305 ret = parents;
315 ret = parents;
306 Py_INCREF(ret);
316 Py_INCREF(ret);
307 quit:
317 quit:
308 Py_XDECREF(fname);
318 Py_XDECREF(fname);
309 Py_XDECREF(cname);
319 Py_XDECREF(cname);
310 Py_XDECREF(entry);
320 Py_XDECREF(entry);
311 Py_XDECREF(parents);
321 Py_XDECREF(parents);
312 return ret;
322 return ret;
313 }
323 }
314
324
315 /*
325 /*
316 * Build a set of non-normal and other parent entries from the dirstate dmap
326 * Build a set of non-normal and other parent entries from the dirstate dmap
317 */
327 */
318 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
328 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
319 {
329 {
320 PyObject *dmap, *fname, *v;
330 PyObject *dmap, *fname, *v;
321 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
331 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
322 Py_ssize_t pos;
332 Py_ssize_t pos;
323
333
324 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
334 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
325 &dmap)) {
335 &dmap)) {
326 goto bail;
336 goto bail;
327 }
337 }
328
338
329 nonnset = PySet_New(NULL);
339 nonnset = PySet_New(NULL);
330 if (nonnset == NULL) {
340 if (nonnset == NULL) {
331 goto bail;
341 goto bail;
332 }
342 }
333
343
334 otherpset = PySet_New(NULL);
344 otherpset = PySet_New(NULL);
335 if (otherpset == NULL) {
345 if (otherpset == NULL) {
336 goto bail;
346 goto bail;
337 }
347 }
338
348
339 pos = 0;
349 pos = 0;
340 while (PyDict_Next(dmap, &pos, &fname, &v)) {
350 while (PyDict_Next(dmap, &pos, &fname, &v)) {
341 dirstateTupleObject *t;
351 dirstateTupleObject *t;
342 if (!dirstate_tuple_check(v)) {
352 if (!dirstate_tuple_check(v)) {
343 PyErr_SetString(PyExc_TypeError,
353 PyErr_SetString(PyExc_TypeError,
344 "expected a dirstate tuple");
354 "expected a dirstate tuple");
345 goto bail;
355 goto bail;
346 }
356 }
347 t = (dirstateTupleObject *)v;
357 t = (dirstateTupleObject *)v;
348
358
349 if (t->state == 'n' && t->size == -2) {
359 if (t->state == 'n' && t->size == -2) {
350 if (PySet_Add(otherpset, fname) == -1) {
360 if (PySet_Add(otherpset, fname) == -1) {
351 goto bail;
361 goto bail;
352 }
362 }
353 }
363 }
354
364
355 if (t->state == 'n' && t->mtime != -1) {
365 if (t->state == 'n' && t->mtime != -1) {
356 continue;
366 continue;
357 }
367 }
358 if (PySet_Add(nonnset, fname) == -1) {
368 if (PySet_Add(nonnset, fname) == -1) {
359 goto bail;
369 goto bail;
360 }
370 }
361 }
371 }
362
372
363 result = Py_BuildValue("(OO)", nonnset, otherpset);
373 result = Py_BuildValue("(OO)", nonnset, otherpset);
364 if (result == NULL) {
374 if (result == NULL) {
365 goto bail;
375 goto bail;
366 }
376 }
367 Py_DECREF(nonnset);
377 Py_DECREF(nonnset);
368 Py_DECREF(otherpset);
378 Py_DECREF(otherpset);
369 return result;
379 return result;
370 bail:
380 bail:
371 Py_XDECREF(nonnset);
381 Py_XDECREF(nonnset);
372 Py_XDECREF(otherpset);
382 Py_XDECREF(otherpset);
373 Py_XDECREF(result);
383 Py_XDECREF(result);
374 return NULL;
384 return NULL;
375 }
385 }
376
386
377 /*
387 /*
378 * Efficiently pack a dirstate object into its on-disk format.
388 * Efficiently pack a dirstate object into its on-disk format.
379 */
389 */
380 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
390 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
381 {
391 {
382 PyObject *packobj = NULL;
392 PyObject *packobj = NULL;
383 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
393 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
384 Py_ssize_t nbytes, pos, l;
394 Py_ssize_t nbytes, pos, l;
385 PyObject *k, *v = NULL, *pn;
395 PyObject *k, *v = NULL, *pn;
386 char *p, *s;
396 char *p, *s;
387 int now;
397 int now;
388
398
389 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
399 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
390 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
400 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
391 &now)) {
401 &now)) {
392 return NULL;
402 return NULL;
393 }
403 }
394
404
395 if (PyTuple_Size(pl) != 2) {
405 if (PyTuple_Size(pl) != 2) {
396 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
406 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
397 return NULL;
407 return NULL;
398 }
408 }
399
409
400 /* Figure out how much we need to allocate. */
410 /* Figure out how much we need to allocate. */
401 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
411 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
402 PyObject *c;
412 PyObject *c;
403 if (!PyBytes_Check(k)) {
413 if (!PyBytes_Check(k)) {
404 PyErr_SetString(PyExc_TypeError, "expected string key");
414 PyErr_SetString(PyExc_TypeError, "expected string key");
405 goto bail;
415 goto bail;
406 }
416 }
407 nbytes += PyBytes_GET_SIZE(k) + 17;
417 nbytes += PyBytes_GET_SIZE(k) + 17;
408 c = PyDict_GetItem(copymap, k);
418 c = PyDict_GetItem(copymap, k);
409 if (c) {
419 if (c) {
410 if (!PyBytes_Check(c)) {
420 if (!PyBytes_Check(c)) {
411 PyErr_SetString(PyExc_TypeError,
421 PyErr_SetString(PyExc_TypeError,
412 "expected string key");
422 "expected string key");
413 goto bail;
423 goto bail;
414 }
424 }
415 nbytes += PyBytes_GET_SIZE(c) + 1;
425 nbytes += PyBytes_GET_SIZE(c) + 1;
416 }
426 }
417 }
427 }
418
428
419 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
429 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
420 if (packobj == NULL) {
430 if (packobj == NULL) {
421 goto bail;
431 goto bail;
422 }
432 }
423
433
424 p = PyBytes_AS_STRING(packobj);
434 p = PyBytes_AS_STRING(packobj);
425
435
426 pn = PyTuple_GET_ITEM(pl, 0);
436 pn = PyTuple_GET_ITEM(pl, 0);
427 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
437 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
428 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
438 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
429 goto bail;
439 goto bail;
430 }
440 }
431 memcpy(p, s, l);
441 memcpy(p, s, l);
432 p += 20;
442 p += 20;
433 pn = PyTuple_GET_ITEM(pl, 1);
443 pn = PyTuple_GET_ITEM(pl, 1);
434 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
444 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
435 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
445 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
436 goto bail;
446 goto bail;
437 }
447 }
438 memcpy(p, s, l);
448 memcpy(p, s, l);
439 p += 20;
449 p += 20;
440
450
441 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
451 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
442 dirstateTupleObject *tuple;
452 dirstateTupleObject *tuple;
443 char state;
453 char state;
444 int mode, size, mtime;
454 int mode, size, mtime;
445 Py_ssize_t len, l;
455 Py_ssize_t len, l;
446 PyObject *o;
456 PyObject *o;
447 char *t;
457 char *t;
448
458
449 if (!dirstate_tuple_check(v)) {
459 if (!dirstate_tuple_check(v)) {
450 PyErr_SetString(PyExc_TypeError,
460 PyErr_SetString(PyExc_TypeError,
451 "expected a dirstate tuple");
461 "expected a dirstate tuple");
452 goto bail;
462 goto bail;
453 }
463 }
454 tuple = (dirstateTupleObject *)v;
464 tuple = (dirstateTupleObject *)v;
455
465
456 state = tuple->state;
466 state = tuple->state;
457 mode = tuple->mode;
467 mode = tuple->mode;
458 size = tuple->size;
468 size = tuple->size;
459 mtime = tuple->mtime;
469 mtime = tuple->mtime;
460 if (state == 'n' && mtime == now) {
470 if (state == 'n' && mtime == now) {
461 /* See pure/parsers.py:pack_dirstate for why we do
471 /* See pure/parsers.py:pack_dirstate for why we do
462 * this. */
472 * this. */
463 mtime = -1;
473 mtime = -1;
464 mtime_unset = (PyObject *)make_dirstate_tuple(
474 mtime_unset = (PyObject *)make_dirstate_tuple(
465 state, mode, size, mtime);
475 state, mode, size, mtime);
466 if (!mtime_unset) {
476 if (!mtime_unset) {
467 goto bail;
477 goto bail;
468 }
478 }
469 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
479 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
470 goto bail;
480 goto bail;
471 }
481 }
472 Py_DECREF(mtime_unset);
482 Py_DECREF(mtime_unset);
473 mtime_unset = NULL;
483 mtime_unset = NULL;
474 }
484 }
475 *p++ = state;
485 *p++ = state;
476 putbe32((uint32_t)mode, p);
486 putbe32((uint32_t)mode, p);
477 putbe32((uint32_t)size, p + 4);
487 putbe32((uint32_t)size, p + 4);
478 putbe32((uint32_t)mtime, p + 8);
488 putbe32((uint32_t)mtime, p + 8);
479 t = p + 12;
489 t = p + 12;
480 p += 16;
490 p += 16;
481 len = PyBytes_GET_SIZE(k);
491 len = PyBytes_GET_SIZE(k);
482 memcpy(p, PyBytes_AS_STRING(k), len);
492 memcpy(p, PyBytes_AS_STRING(k), len);
483 p += len;
493 p += len;
484 o = PyDict_GetItem(copymap, k);
494 o = PyDict_GetItem(copymap, k);
485 if (o) {
495 if (o) {
486 *p++ = '\0';
496 *p++ = '\0';
487 l = PyBytes_GET_SIZE(o);
497 l = PyBytes_GET_SIZE(o);
488 memcpy(p, PyBytes_AS_STRING(o), l);
498 memcpy(p, PyBytes_AS_STRING(o), l);
489 p += l;
499 p += l;
490 len += l + 1;
500 len += l + 1;
491 }
501 }
492 putbe32((uint32_t)len, t);
502 putbe32((uint32_t)len, t);
493 }
503 }
494
504
495 pos = p - PyBytes_AS_STRING(packobj);
505 pos = p - PyBytes_AS_STRING(packobj);
496 if (pos != nbytes) {
506 if (pos != nbytes) {
497 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
507 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
498 (long)pos, (long)nbytes);
508 (long)pos, (long)nbytes);
499 goto bail;
509 goto bail;
500 }
510 }
501
511
502 return packobj;
512 return packobj;
503 bail:
513 bail:
504 Py_XDECREF(mtime_unset);
514 Py_XDECREF(mtime_unset);
505 Py_XDECREF(packobj);
515 Py_XDECREF(packobj);
506 Py_XDECREF(v);
516 Py_XDECREF(v);
507 return NULL;
517 return NULL;
508 }
518 }
509
519
510 #define BUMPED_FIX 1
520 #define BUMPED_FIX 1
511 #define USING_SHA_256 2
521 #define USING_SHA_256 2
512 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
522 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
513
523
514 static PyObject *readshas(const char *source, unsigned char num,
524 static PyObject *readshas(const char *source, unsigned char num,
515 Py_ssize_t hashwidth)
525 Py_ssize_t hashwidth)
516 {
526 {
517 int i;
527 int i;
518 PyObject *list = PyTuple_New(num);
528 PyObject *list = PyTuple_New(num);
519 if (list == NULL) {
529 if (list == NULL) {
520 return NULL;
530 return NULL;
521 }
531 }
522 for (i = 0; i < num; i++) {
532 for (i = 0; i < num; i++) {
523 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
533 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
524 if (hash == NULL) {
534 if (hash == NULL) {
525 Py_DECREF(list);
535 Py_DECREF(list);
526 return NULL;
536 return NULL;
527 }
537 }
528 PyTuple_SET_ITEM(list, i, hash);
538 PyTuple_SET_ITEM(list, i, hash);
529 source += hashwidth;
539 source += hashwidth;
530 }
540 }
531 return list;
541 return list;
532 }
542 }
533
543
534 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
544 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
535 uint32_t *msize)
545 uint32_t *msize)
536 {
546 {
537 const char *data = databegin;
547 const char *data = databegin;
538 const char *meta;
548 const char *meta;
539
549
540 double mtime;
550 double mtime;
541 int16_t tz;
551 int16_t tz;
542 uint16_t flags;
552 uint16_t flags;
543 unsigned char nsuccs, nparents, nmetadata;
553 unsigned char nsuccs, nparents, nmetadata;
544 Py_ssize_t hashwidth = 20;
554 Py_ssize_t hashwidth = 20;
545
555
546 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
556 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
547 PyObject *metadata = NULL, *ret = NULL;
557 PyObject *metadata = NULL, *ret = NULL;
548 int i;
558 int i;
549
559
550 if (data + FM1_HEADER_SIZE > dataend) {
560 if (data + FM1_HEADER_SIZE > dataend) {
551 goto overflow;
561 goto overflow;
552 }
562 }
553
563
554 *msize = getbe32(data);
564 *msize = getbe32(data);
555 data += 4;
565 data += 4;
556 mtime = getbefloat64(data);
566 mtime = getbefloat64(data);
557 data += 8;
567 data += 8;
558 tz = getbeint16(data);
568 tz = getbeint16(data);
559 data += 2;
569 data += 2;
560 flags = getbeuint16(data);
570 flags = getbeuint16(data);
561 data += 2;
571 data += 2;
562
572
563 if (flags & USING_SHA_256) {
573 if (flags & USING_SHA_256) {
564 hashwidth = 32;
574 hashwidth = 32;
565 }
575 }
566
576
567 nsuccs = (unsigned char)(*data++);
577 nsuccs = (unsigned char)(*data++);
568 nparents = (unsigned char)(*data++);
578 nparents = (unsigned char)(*data++);
569 nmetadata = (unsigned char)(*data++);
579 nmetadata = (unsigned char)(*data++);
570
580
571 if (databegin + *msize > dataend) {
581 if (databegin + *msize > dataend) {
572 goto overflow;
582 goto overflow;
573 }
583 }
574 dataend = databegin + *msize; /* narrow down to marker size */
584 dataend = databegin + *msize; /* narrow down to marker size */
575
585
576 if (data + hashwidth > dataend) {
586 if (data + hashwidth > dataend) {
577 goto overflow;
587 goto overflow;
578 }
588 }
579 prec = PyBytes_FromStringAndSize(data, hashwidth);
589 prec = PyBytes_FromStringAndSize(data, hashwidth);
580 data += hashwidth;
590 data += hashwidth;
581 if (prec == NULL) {
591 if (prec == NULL) {
582 goto bail;
592 goto bail;
583 }
593 }
584
594
585 if (data + nsuccs * hashwidth > dataend) {
595 if (data + nsuccs * hashwidth > dataend) {
586 goto overflow;
596 goto overflow;
587 }
597 }
588 succs = readshas(data, nsuccs, hashwidth);
598 succs = readshas(data, nsuccs, hashwidth);
589 if (succs == NULL) {
599 if (succs == NULL) {
590 goto bail;
600 goto bail;
591 }
601 }
592 data += nsuccs * hashwidth;
602 data += nsuccs * hashwidth;
593
603
594 if (nparents == 1 || nparents == 2) {
604 if (nparents == 1 || nparents == 2) {
595 if (data + nparents * hashwidth > dataend) {
605 if (data + nparents * hashwidth > dataend) {
596 goto overflow;
606 goto overflow;
597 }
607 }
598 parents = readshas(data, nparents, hashwidth);
608 parents = readshas(data, nparents, hashwidth);
599 if (parents == NULL) {
609 if (parents == NULL) {
600 goto bail;
610 goto bail;
601 }
611 }
602 data += nparents * hashwidth;
612 data += nparents * hashwidth;
603 } else {
613 } else {
604 parents = Py_None;
614 parents = Py_None;
605 Py_INCREF(parents);
615 Py_INCREF(parents);
606 }
616 }
607
617
608 if (data + 2 * nmetadata > dataend) {
618 if (data + 2 * nmetadata > dataend) {
609 goto overflow;
619 goto overflow;
610 }
620 }
611 meta = data + (2 * nmetadata);
621 meta = data + (2 * nmetadata);
612 metadata = PyTuple_New(nmetadata);
622 metadata = PyTuple_New(nmetadata);
613 if (metadata == NULL) {
623 if (metadata == NULL) {
614 goto bail;
624 goto bail;
615 }
625 }
616 for (i = 0; i < nmetadata; i++) {
626 for (i = 0; i < nmetadata; i++) {
617 PyObject *tmp, *left = NULL, *right = NULL;
627 PyObject *tmp, *left = NULL, *right = NULL;
618 Py_ssize_t leftsize = (unsigned char)(*data++);
628 Py_ssize_t leftsize = (unsigned char)(*data++);
619 Py_ssize_t rightsize = (unsigned char)(*data++);
629 Py_ssize_t rightsize = (unsigned char)(*data++);
620 if (meta + leftsize + rightsize > dataend) {
630 if (meta + leftsize + rightsize > dataend) {
621 goto overflow;
631 goto overflow;
622 }
632 }
623 left = PyBytes_FromStringAndSize(meta, leftsize);
633 left = PyBytes_FromStringAndSize(meta, leftsize);
624 meta += leftsize;
634 meta += leftsize;
625 right = PyBytes_FromStringAndSize(meta, rightsize);
635 right = PyBytes_FromStringAndSize(meta, rightsize);
626 meta += rightsize;
636 meta += rightsize;
627 tmp = PyTuple_New(2);
637 tmp = PyTuple_New(2);
628 if (!left || !right || !tmp) {
638 if (!left || !right || !tmp) {
629 Py_XDECREF(left);
639 Py_XDECREF(left);
630 Py_XDECREF(right);
640 Py_XDECREF(right);
631 Py_XDECREF(tmp);
641 Py_XDECREF(tmp);
632 goto bail;
642 goto bail;
633 }
643 }
634 PyTuple_SET_ITEM(tmp, 0, left);
644 PyTuple_SET_ITEM(tmp, 0, left);
635 PyTuple_SET_ITEM(tmp, 1, right);
645 PyTuple_SET_ITEM(tmp, 1, right);
636 PyTuple_SET_ITEM(metadata, i, tmp);
646 PyTuple_SET_ITEM(metadata, i, tmp);
637 }
647 }
638 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
648 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
639 (int)tz * 60, parents);
649 (int)tz * 60, parents);
640 goto bail; /* return successfully */
650 goto bail; /* return successfully */
641
651
642 overflow:
652 overflow:
643 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
653 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
644 bail:
654 bail:
645 Py_XDECREF(prec);
655 Py_XDECREF(prec);
646 Py_XDECREF(succs);
656 Py_XDECREF(succs);
647 Py_XDECREF(metadata);
657 Py_XDECREF(metadata);
648 Py_XDECREF(parents);
658 Py_XDECREF(parents);
649 return ret;
659 return ret;
650 }
660 }
651
661
652 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
662 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
653 {
663 {
654 const char *data, *dataend;
664 const char *data, *dataend;
655 Py_ssize_t datalen, offset, stop;
665 Py_ssize_t datalen, offset, stop;
656 PyObject *markers = NULL;
666 PyObject *markers = NULL;
657
667
658 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
668 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
659 &offset, &stop)) {
669 &offset, &stop)) {
660 return NULL;
670 return NULL;
661 }
671 }
662 if (offset < 0) {
672 if (offset < 0) {
663 PyErr_SetString(PyExc_ValueError,
673 PyErr_SetString(PyExc_ValueError,
664 "invalid negative offset in fm1readmarkers");
674 "invalid negative offset in fm1readmarkers");
665 return NULL;
675 return NULL;
666 }
676 }
667 if (stop > datalen) {
677 if (stop > datalen) {
668 PyErr_SetString(
678 PyErr_SetString(
669 PyExc_ValueError,
679 PyExc_ValueError,
670 "stop longer than data length in fm1readmarkers");
680 "stop longer than data length in fm1readmarkers");
671 return NULL;
681 return NULL;
672 }
682 }
673 dataend = data + datalen;
683 dataend = data + datalen;
674 data += offset;
684 data += offset;
675 markers = PyList_New(0);
685 markers = PyList_New(0);
676 if (!markers) {
686 if (!markers) {
677 return NULL;
687 return NULL;
678 }
688 }
679 while (offset < stop) {
689 while (offset < stop) {
680 uint32_t msize;
690 uint32_t msize;
681 int error;
691 int error;
682 PyObject *record = fm1readmarker(data, dataend, &msize);
692 PyObject *record = fm1readmarker(data, dataend, &msize);
683 if (!record) {
693 if (!record) {
684 goto bail;
694 goto bail;
685 }
695 }
686 error = PyList_Append(markers, record);
696 error = PyList_Append(markers, record);
687 Py_DECREF(record);
697 Py_DECREF(record);
688 if (error) {
698 if (error) {
689 goto bail;
699 goto bail;
690 }
700 }
691 data += msize;
701 data += msize;
692 offset += msize;
702 offset += msize;
693 }
703 }
694 return markers;
704 return markers;
695 bail:
705 bail:
696 Py_DECREF(markers);
706 Py_DECREF(markers);
697 return NULL;
707 return NULL;
698 }
708 }
699
709
700 static char parsers_doc[] = "Efficient content parsing.";
710 static char parsers_doc[] = "Efficient content parsing.";
701
711
702 PyObject *encodedir(PyObject *self, PyObject *args);
712 PyObject *encodedir(PyObject *self, PyObject *args);
703 PyObject *pathencode(PyObject *self, PyObject *args);
713 PyObject *pathencode(PyObject *self, PyObject *args);
704 PyObject *lowerencode(PyObject *self, PyObject *args);
714 PyObject *lowerencode(PyObject *self, PyObject *args);
705 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
715 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
706
716
707 static PyMethodDef methods[] = {
717 static PyMethodDef methods[] = {
708 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
718 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
709 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
719 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
710 "create a set containing non-normal and other parent entries of given "
720 "create a set containing non-normal and other parent entries of given "
711 "dirstate\n"},
721 "dirstate\n"},
712 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
722 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
713 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
723 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
714 "parse a revlog index\n"},
724 "parse a revlog index\n"},
715 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
725 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
716 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
726 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
717 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
727 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
718 {"dict_new_presized", dict_new_presized, METH_VARARGS,
728 {"dict_new_presized", dict_new_presized, METH_VARARGS,
719 "construct a dict with an expected size\n"},
729 "construct a dict with an expected size\n"},
720 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
730 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
721 "make file foldmap\n"},
731 "make file foldmap\n"},
722 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
732 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
723 "escape a UTF-8 byte string to JSON (fast path)\n"},
733 "escape a UTF-8 byte string to JSON (fast path)\n"},
724 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
734 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
725 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
735 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
726 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
736 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
727 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
737 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
728 "parse v1 obsolete markers\n"},
738 "parse v1 obsolete markers\n"},
729 {NULL, NULL}};
739 {NULL, NULL}};
730
740
731 void dirs_module_init(PyObject *mod);
741 void dirs_module_init(PyObject *mod);
732 void manifest_module_init(PyObject *mod);
742 void manifest_module_init(PyObject *mod);
733 void revlog_module_init(PyObject *mod);
743 void revlog_module_init(PyObject *mod);
734
744
735 static const int version = 20;
745 static const int version = 20;
736
746
737 static void module_init(PyObject *mod)
747 static void module_init(PyObject *mod)
738 {
748 {
739 PyObject *capsule = NULL;
749 PyObject *capsule = NULL;
740 PyModule_AddIntConstant(mod, "version", version);
750 PyModule_AddIntConstant(mod, "version", version);
741
751
742 /* This module constant has two purposes. First, it lets us unit test
752 /* This module constant has two purposes. First, it lets us unit test
743 * the ImportError raised without hard-coding any error text. This
753 * the ImportError raised without hard-coding any error text. This
744 * means we can change the text in the future without breaking tests,
754 * means we can change the text in the future without breaking tests,
745 * even across changesets without a recompile. Second, its presence
755 * even across changesets without a recompile. Second, its presence
746 * can be used to determine whether the version-checking logic is
756 * can be used to determine whether the version-checking logic is
747 * present, which also helps in testing across changesets without a
757 * present, which also helps in testing across changesets without a
748 * recompile. Note that this means the pure-Python version of parsers
758 * recompile. Note that this means the pure-Python version of parsers
749 * should not have this module constant. */
759 * should not have this module constant. */
750 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
760 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
751
761
752 dirs_module_init(mod);
762 dirs_module_init(mod);
753 manifest_module_init(mod);
763 manifest_module_init(mod);
754 revlog_module_init(mod);
764 revlog_module_init(mod);
755
765
756 capsule = PyCapsule_New(
766 capsule = PyCapsule_New(
757 make_dirstate_tuple,
767 make_dirstate_tuple,
758 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
768 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
759 if (capsule != NULL)
769 if (capsule != NULL)
760 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
770 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
761
771
762 if (PyType_Ready(&dirstateTupleType) < 0) {
772 if (PyType_Ready(&dirstateTupleType) < 0) {
763 return;
773 return;
764 }
774 }
765 Py_INCREF(&dirstateTupleType);
775 Py_INCREF(&dirstateTupleType);
766 PyModule_AddObject(mod, "dirstatetuple",
776 PyModule_AddObject(mod, "dirstatetuple",
767 (PyObject *)&dirstateTupleType);
777 (PyObject *)&dirstateTupleType);
768 }
778 }
769
779
770 static int check_python_version(void)
780 static int check_python_version(void)
771 {
781 {
772 PyObject *sys = PyImport_ImportModule("sys"), *ver;
782 PyObject *sys = PyImport_ImportModule("sys"), *ver;
773 long hexversion;
783 long hexversion;
774 if (!sys) {
784 if (!sys) {
775 return -1;
785 return -1;
776 }
786 }
777 ver = PyObject_GetAttrString(sys, "hexversion");
787 ver = PyObject_GetAttrString(sys, "hexversion");
778 Py_DECREF(sys);
788 Py_DECREF(sys);
779 if (!ver) {
789 if (!ver) {
780 return -1;
790 return -1;
781 }
791 }
782 hexversion = PyInt_AsLong(ver);
792 hexversion = PyInt_AsLong(ver);
783 Py_DECREF(ver);
793 Py_DECREF(ver);
784 /* sys.hexversion is a 32-bit number by default, so the -1 case
794 /* sys.hexversion is a 32-bit number by default, so the -1 case
785 * should only occur in unusual circumstances (e.g. if sys.hexversion
795 * should only occur in unusual circumstances (e.g. if sys.hexversion
786 * is manually set to an invalid value). */
796 * is manually set to an invalid value). */
787 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
797 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
788 PyErr_Format(PyExc_ImportError,
798 PyErr_Format(PyExc_ImportError,
789 "%s: The Mercurial extension "
799 "%s: The Mercurial extension "
790 "modules were compiled with Python " PY_VERSION
800 "modules were compiled with Python " PY_VERSION
791 ", but "
801 ", but "
792 "Mercurial is currently using Python with "
802 "Mercurial is currently using Python with "
793 "sys.hexversion=%ld: "
803 "sys.hexversion=%ld: "
794 "Python %s\n at: %s",
804 "Python %s\n at: %s",
795 versionerrortext, hexversion, Py_GetVersion(),
805 versionerrortext, hexversion, Py_GetVersion(),
796 Py_GetProgramFullPath());
806 Py_GetProgramFullPath());
797 return -1;
807 return -1;
798 }
808 }
799 return 0;
809 return 0;
800 }
810 }
801
811
802 #ifdef IS_PY3K
812 #ifdef IS_PY3K
803 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
813 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
804 parsers_doc, -1, methods};
814 parsers_doc, -1, methods};
805
815
806 PyMODINIT_FUNC PyInit_parsers(void)
816 PyMODINIT_FUNC PyInit_parsers(void)
807 {
817 {
808 PyObject *mod;
818 PyObject *mod;
809
819
810 if (check_python_version() == -1)
820 if (check_python_version() == -1)
811 return NULL;
821 return NULL;
812 mod = PyModule_Create(&parsers_module);
822 mod = PyModule_Create(&parsers_module);
813 module_init(mod);
823 module_init(mod);
814 return mod;
824 return mod;
815 }
825 }
816 #else
826 #else
817 PyMODINIT_FUNC initparsers(void)
827 PyMODINIT_FUNC initparsers(void)
818 {
828 {
819 PyObject *mod;
829 PyObject *mod;
820
830
821 if (check_python_version() == -1) {
831 if (check_python_version() == -1) {
822 return;
832 return;
823 }
833 }
824 mod = Py_InitModule3("parsers", methods, parsers_doc);
834 mod = Py_InitModule3("parsers", methods, parsers_doc);
825 module_init(mod);
835 module_init(mod);
826 }
836 }
827 #endif
837 #endif
@@ -1,1453 +1,1451
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .pycompat import delattr
17 from .pycompat import delattr
18
18
19 from hgdemandimport import tracing
19 from hgdemandimport import tracing
20
20
21 from . import (
21 from . import (
22 dirstatemap,
22 dirstatemap,
23 encoding,
23 encoding,
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 pathutil,
26 pathutil,
27 policy,
27 policy,
28 pycompat,
28 pycompat,
29 scmutil,
29 scmutil,
30 sparse,
30 sparse,
31 util,
31 util,
32 )
32 )
33
33
34 from .interfaces import (
34 from .interfaces import (
35 dirstate as intdirstate,
35 dirstate as intdirstate,
36 util as interfaceutil,
36 util as interfaceutil,
37 )
37 )
38
38
39 parsers = policy.importmod('parsers')
39 parsers = policy.importmod('parsers')
40 rustmod = policy.importrust('dirstate')
40 rustmod = policy.importrust('dirstate')
41
41
42 SUPPORTS_DIRSTATE_V2 = rustmod is not None
42 SUPPORTS_DIRSTATE_V2 = rustmod is not None
43
43
44 propertycache = util.propertycache
44 propertycache = util.propertycache
45 filecache = scmutil.filecache
45 filecache = scmutil.filecache
46 _rangemask = 0x7FFFFFFF
46 _rangemask = 0x7FFFFFFF
47
47
48 dirstatetuple = parsers.dirstatetuple
48 dirstatetuple = parsers.dirstatetuple
49
49
50
50
51 # a special value used internally for `size` if the file come from the other parent
51 # a special value used internally for `size` if the file come from the other parent
52 FROM_P2 = dirstatemap.FROM_P2
52 FROM_P2 = dirstatemap.FROM_P2
53
53
54 # a special value used internally for `size` if the file is modified/merged/added
54 # a special value used internally for `size` if the file is modified/merged/added
55 NONNORMAL = dirstatemap.NONNORMAL
55 NONNORMAL = dirstatemap.NONNORMAL
56
56
57 # a special value used internally for `time` if the time is ambigeous
57 # a special value used internally for `time` if the time is ambigeous
58 AMBIGUOUS_TIME = dirstatemap.AMBIGUOUS_TIME
58 AMBIGUOUS_TIME = dirstatemap.AMBIGUOUS_TIME
59
59
60
60
61 class repocache(filecache):
61 class repocache(filecache):
62 """filecache for files in .hg/"""
62 """filecache for files in .hg/"""
63
63
64 def join(self, obj, fname):
64 def join(self, obj, fname):
65 return obj._opener.join(fname)
65 return obj._opener.join(fname)
66
66
67
67
68 class rootcache(filecache):
68 class rootcache(filecache):
69 """filecache for files in the repository root"""
69 """filecache for files in the repository root"""
70
70
71 def join(self, obj, fname):
71 def join(self, obj, fname):
72 return obj._join(fname)
72 return obj._join(fname)
73
73
74
74
75 def _getfsnow(vfs):
75 def _getfsnow(vfs):
76 '''Get "now" timestamp on filesystem'''
76 '''Get "now" timestamp on filesystem'''
77 tmpfd, tmpname = vfs.mkstemp()
77 tmpfd, tmpname = vfs.mkstemp()
78 try:
78 try:
79 return os.fstat(tmpfd)[stat.ST_MTIME]
79 return os.fstat(tmpfd)[stat.ST_MTIME]
80 finally:
80 finally:
81 os.close(tmpfd)
81 os.close(tmpfd)
82 vfs.unlink(tmpname)
82 vfs.unlink(tmpname)
83
83
84
84
85 @interfaceutil.implementer(intdirstate.idirstate)
85 @interfaceutil.implementer(intdirstate.idirstate)
86 class dirstate(object):
86 class dirstate(object):
87 def __init__(
87 def __init__(
88 self,
88 self,
89 opener,
89 opener,
90 ui,
90 ui,
91 root,
91 root,
92 validate,
92 validate,
93 sparsematchfn,
93 sparsematchfn,
94 nodeconstants,
94 nodeconstants,
95 use_dirstate_v2,
95 use_dirstate_v2,
96 ):
96 ):
97 """Create a new dirstate object.
97 """Create a new dirstate object.
98
98
99 opener is an open()-like callable that can be used to open the
99 opener is an open()-like callable that can be used to open the
100 dirstate file; root is the root of the directory tracked by
100 dirstate file; root is the root of the directory tracked by
101 the dirstate.
101 the dirstate.
102 """
102 """
103 self._use_dirstate_v2 = use_dirstate_v2
103 self._use_dirstate_v2 = use_dirstate_v2
104 self._nodeconstants = nodeconstants
104 self._nodeconstants = nodeconstants
105 self._opener = opener
105 self._opener = opener
106 self._validate = validate
106 self._validate = validate
107 self._root = root
107 self._root = root
108 self._sparsematchfn = sparsematchfn
108 self._sparsematchfn = sparsematchfn
109 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
109 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
110 # UNC path pointing to root share (issue4557)
110 # UNC path pointing to root share (issue4557)
111 self._rootdir = pathutil.normasprefix(root)
111 self._rootdir = pathutil.normasprefix(root)
112 self._dirty = False
112 self._dirty = False
113 self._lastnormaltime = 0
113 self._lastnormaltime = 0
114 self._ui = ui
114 self._ui = ui
115 self._filecache = {}
115 self._filecache = {}
116 self._parentwriters = 0
116 self._parentwriters = 0
117 self._filename = b'dirstate'
117 self._filename = b'dirstate'
118 self._pendingfilename = b'%s.pending' % self._filename
118 self._pendingfilename = b'%s.pending' % self._filename
119 self._plchangecallbacks = {}
119 self._plchangecallbacks = {}
120 self._origpl = None
120 self._origpl = None
121 self._updatedfiles = set()
121 self._updatedfiles = set()
122 self._mapcls = dirstatemap.dirstatemap
122 self._mapcls = dirstatemap.dirstatemap
123 # Access and cache cwd early, so we don't access it for the first time
123 # Access and cache cwd early, so we don't access it for the first time
124 # after a working-copy update caused it to not exist (accessing it then
124 # after a working-copy update caused it to not exist (accessing it then
125 # raises an exception).
125 # raises an exception).
126 self._cwd
126 self._cwd
127
127
128 def prefetch_parents(self):
128 def prefetch_parents(self):
129 """make sure the parents are loaded
129 """make sure the parents are loaded
130
130
131 Used to avoid a race condition.
131 Used to avoid a race condition.
132 """
132 """
133 self._pl
133 self._pl
134
134
135 @contextlib.contextmanager
135 @contextlib.contextmanager
136 def parentchange(self):
136 def parentchange(self):
137 """Context manager for handling dirstate parents.
137 """Context manager for handling dirstate parents.
138
138
139 If an exception occurs in the scope of the context manager,
139 If an exception occurs in the scope of the context manager,
140 the incoherent dirstate won't be written when wlock is
140 the incoherent dirstate won't be written when wlock is
141 released.
141 released.
142 """
142 """
143 self._parentwriters += 1
143 self._parentwriters += 1
144 yield
144 yield
145 # Typically we want the "undo" step of a context manager in a
145 # Typically we want the "undo" step of a context manager in a
146 # finally block so it happens even when an exception
146 # finally block so it happens even when an exception
147 # occurs. In this case, however, we only want to decrement
147 # occurs. In this case, however, we only want to decrement
148 # parentwriters if the code in the with statement exits
148 # parentwriters if the code in the with statement exits
149 # normally, so we don't have a try/finally here on purpose.
149 # normally, so we don't have a try/finally here on purpose.
150 self._parentwriters -= 1
150 self._parentwriters -= 1
151
151
152 def pendingparentchange(self):
152 def pendingparentchange(self):
153 """Returns true if the dirstate is in the middle of a set of changes
153 """Returns true if the dirstate is in the middle of a set of changes
154 that modify the dirstate parent.
154 that modify the dirstate parent.
155 """
155 """
156 return self._parentwriters > 0
156 return self._parentwriters > 0
157
157
158 @propertycache
158 @propertycache
159 def _map(self):
159 def _map(self):
160 """Return the dirstate contents (see documentation for dirstatemap)."""
160 """Return the dirstate contents (see documentation for dirstatemap)."""
161 self._map = self._mapcls(
161 self._map = self._mapcls(
162 self._ui,
162 self._ui,
163 self._opener,
163 self._opener,
164 self._root,
164 self._root,
165 self._nodeconstants,
165 self._nodeconstants,
166 self._use_dirstate_v2,
166 self._use_dirstate_v2,
167 )
167 )
168 return self._map
168 return self._map
169
169
170 @property
170 @property
171 def _sparsematcher(self):
171 def _sparsematcher(self):
172 """The matcher for the sparse checkout.
172 """The matcher for the sparse checkout.
173
173
174 The working directory may not include every file from a manifest. The
174 The working directory may not include every file from a manifest. The
175 matcher obtained by this property will match a path if it is to be
175 matcher obtained by this property will match a path if it is to be
176 included in the working directory.
176 included in the working directory.
177 """
177 """
178 # TODO there is potential to cache this property. For now, the matcher
178 # TODO there is potential to cache this property. For now, the matcher
179 # is resolved on every access. (But the called function does use a
179 # is resolved on every access. (But the called function does use a
180 # cache to keep the lookup fast.)
180 # cache to keep the lookup fast.)
181 return self._sparsematchfn()
181 return self._sparsematchfn()
182
182
183 @repocache(b'branch')
183 @repocache(b'branch')
184 def _branch(self):
184 def _branch(self):
185 try:
185 try:
186 return self._opener.read(b"branch").strip() or b"default"
186 return self._opener.read(b"branch").strip() or b"default"
187 except IOError as inst:
187 except IOError as inst:
188 if inst.errno != errno.ENOENT:
188 if inst.errno != errno.ENOENT:
189 raise
189 raise
190 return b"default"
190 return b"default"
191
191
192 @property
192 @property
193 def _pl(self):
193 def _pl(self):
194 return self._map.parents()
194 return self._map.parents()
195
195
196 def hasdir(self, d):
196 def hasdir(self, d):
197 return self._map.hastrackeddir(d)
197 return self._map.hastrackeddir(d)
198
198
199 @rootcache(b'.hgignore')
199 @rootcache(b'.hgignore')
200 def _ignore(self):
200 def _ignore(self):
201 files = self._ignorefiles()
201 files = self._ignorefiles()
202 if not files:
202 if not files:
203 return matchmod.never()
203 return matchmod.never()
204
204
205 pats = [b'include:%s' % f for f in files]
205 pats = [b'include:%s' % f for f in files]
206 return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
206 return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
207
207
208 @propertycache
208 @propertycache
209 def _slash(self):
209 def _slash(self):
210 return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
210 return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
211
211
212 @propertycache
212 @propertycache
213 def _checklink(self):
213 def _checklink(self):
214 return util.checklink(self._root)
214 return util.checklink(self._root)
215
215
216 @propertycache
216 @propertycache
217 def _checkexec(self):
217 def _checkexec(self):
218 return bool(util.checkexec(self._root))
218 return bool(util.checkexec(self._root))
219
219
220 @propertycache
220 @propertycache
221 def _checkcase(self):
221 def _checkcase(self):
222 return not util.fscasesensitive(self._join(b'.hg'))
222 return not util.fscasesensitive(self._join(b'.hg'))
223
223
224 def _join(self, f):
224 def _join(self, f):
225 # much faster than os.path.join()
225 # much faster than os.path.join()
226 # it's safe because f is always a relative path
226 # it's safe because f is always a relative path
227 return self._rootdir + f
227 return self._rootdir + f
228
228
229 def flagfunc(self, buildfallback):
229 def flagfunc(self, buildfallback):
230 if self._checklink and self._checkexec:
230 if self._checklink and self._checkexec:
231
231
232 def f(x):
232 def f(x):
233 try:
233 try:
234 st = os.lstat(self._join(x))
234 st = os.lstat(self._join(x))
235 if util.statislink(st):
235 if util.statislink(st):
236 return b'l'
236 return b'l'
237 if util.statisexec(st):
237 if util.statisexec(st):
238 return b'x'
238 return b'x'
239 except OSError:
239 except OSError:
240 pass
240 pass
241 return b''
241 return b''
242
242
243 return f
243 return f
244
244
245 fallback = buildfallback()
245 fallback = buildfallback()
246 if self._checklink:
246 if self._checklink:
247
247
248 def f(x):
248 def f(x):
249 if os.path.islink(self._join(x)):
249 if os.path.islink(self._join(x)):
250 return b'l'
250 return b'l'
251 if b'x' in fallback(x):
251 if b'x' in fallback(x):
252 return b'x'
252 return b'x'
253 return b''
253 return b''
254
254
255 return f
255 return f
256 if self._checkexec:
256 if self._checkexec:
257
257
258 def f(x):
258 def f(x):
259 if b'l' in fallback(x):
259 if b'l' in fallback(x):
260 return b'l'
260 return b'l'
261 if util.isexec(self._join(x)):
261 if util.isexec(self._join(x)):
262 return b'x'
262 return b'x'
263 return b''
263 return b''
264
264
265 return f
265 return f
266 else:
266 else:
267 return fallback
267 return fallback
268
268
269 @propertycache
269 @propertycache
270 def _cwd(self):
270 def _cwd(self):
271 # internal config: ui.forcecwd
271 # internal config: ui.forcecwd
272 forcecwd = self._ui.config(b'ui', b'forcecwd')
272 forcecwd = self._ui.config(b'ui', b'forcecwd')
273 if forcecwd:
273 if forcecwd:
274 return forcecwd
274 return forcecwd
275 return encoding.getcwd()
275 return encoding.getcwd()
276
276
277 def getcwd(self):
277 def getcwd(self):
278 """Return the path from which a canonical path is calculated.
278 """Return the path from which a canonical path is calculated.
279
279
280 This path should be used to resolve file patterns or to convert
280 This path should be used to resolve file patterns or to convert
281 canonical paths back to file paths for display. It shouldn't be
281 canonical paths back to file paths for display. It shouldn't be
282 used to get real file paths. Use vfs functions instead.
282 used to get real file paths. Use vfs functions instead.
283 """
283 """
284 cwd = self._cwd
284 cwd = self._cwd
285 if cwd == self._root:
285 if cwd == self._root:
286 return b''
286 return b''
287 # self._root ends with a path separator if self._root is '/' or 'C:\'
287 # self._root ends with a path separator if self._root is '/' or 'C:\'
288 rootsep = self._root
288 rootsep = self._root
289 if not util.endswithsep(rootsep):
289 if not util.endswithsep(rootsep):
290 rootsep += pycompat.ossep
290 rootsep += pycompat.ossep
291 if cwd.startswith(rootsep):
291 if cwd.startswith(rootsep):
292 return cwd[len(rootsep) :]
292 return cwd[len(rootsep) :]
293 else:
293 else:
294 # we're outside the repo. return an absolute path.
294 # we're outside the repo. return an absolute path.
295 return cwd
295 return cwd
296
296
297 def pathto(self, f, cwd=None):
297 def pathto(self, f, cwd=None):
298 if cwd is None:
298 if cwd is None:
299 cwd = self.getcwd()
299 cwd = self.getcwd()
300 path = util.pathto(self._root, cwd, f)
300 path = util.pathto(self._root, cwd, f)
301 if self._slash:
301 if self._slash:
302 return util.pconvert(path)
302 return util.pconvert(path)
303 return path
303 return path
304
304
305 def __getitem__(self, key):
305 def __getitem__(self, key):
306 """Return the current state of key (a filename) in the dirstate.
306 """Return the current state of key (a filename) in the dirstate.
307
307
308 States are:
308 States are:
309 n normal
309 n normal
310 m needs merging
310 m needs merging
311 r marked for removal
311 r marked for removal
312 a marked for addition
312 a marked for addition
313 ? not tracked
313 ? not tracked
314
314
315 XXX The "state" is a bit obscure to be in the "public" API. we should
315 XXX The "state" is a bit obscure to be in the "public" API. we should
316 consider migrating all user of this to going through the dirstate entry
316 consider migrating all user of this to going through the dirstate entry
317 instead.
317 instead.
318 """
318 """
319 entry = self._map.get(key)
319 entry = self._map.get(key)
320 if entry is not None:
320 if entry is not None:
321 return entry.state
321 return entry.state
322 return b'?'
322 return b'?'
323
323
324 def __contains__(self, key):
324 def __contains__(self, key):
325 return key in self._map
325 return key in self._map
326
326
327 def __iter__(self):
327 def __iter__(self):
328 return iter(sorted(self._map))
328 return iter(sorted(self._map))
329
329
330 def items(self):
330 def items(self):
331 return pycompat.iteritems(self._map)
331 return pycompat.iteritems(self._map)
332
332
333 iteritems = items
333 iteritems = items
334
334
335 def directories(self):
335 def directories(self):
336 return self._map.directories()
336 return self._map.directories()
337
337
338 def parents(self):
338 def parents(self):
339 return [self._validate(p) for p in self._pl]
339 return [self._validate(p) for p in self._pl]
340
340
341 def p1(self):
341 def p1(self):
342 return self._validate(self._pl[0])
342 return self._validate(self._pl[0])
343
343
344 def p2(self):
344 def p2(self):
345 return self._validate(self._pl[1])
345 return self._validate(self._pl[1])
346
346
347 @property
347 @property
348 def in_merge(self):
348 def in_merge(self):
349 """True if a merge is in progress"""
349 """True if a merge is in progress"""
350 return self._pl[1] != self._nodeconstants.nullid
350 return self._pl[1] != self._nodeconstants.nullid
351
351
352 def branch(self):
352 def branch(self):
353 return encoding.tolocal(self._branch)
353 return encoding.tolocal(self._branch)
354
354
355 def setparents(self, p1, p2=None):
355 def setparents(self, p1, p2=None):
356 """Set dirstate parents to p1 and p2.
356 """Set dirstate parents to p1 and p2.
357
357
358 When moving from two parents to one, "merged" entries a
358 When moving from two parents to one, "merged" entries a
359 adjusted to normal and previous copy records discarded and
359 adjusted to normal and previous copy records discarded and
360 returned by the call.
360 returned by the call.
361
361
362 See localrepo.setparents()
362 See localrepo.setparents()
363 """
363 """
364 if p2 is None:
364 if p2 is None:
365 p2 = self._nodeconstants.nullid
365 p2 = self._nodeconstants.nullid
366 if self._parentwriters == 0:
366 if self._parentwriters == 0:
367 raise ValueError(
367 raise ValueError(
368 b"cannot set dirstate parent outside of "
368 b"cannot set dirstate parent outside of "
369 b"dirstate.parentchange context manager"
369 b"dirstate.parentchange context manager"
370 )
370 )
371
371
372 self._dirty = True
372 self._dirty = True
373 oldp2 = self._pl[1]
373 oldp2 = self._pl[1]
374 if self._origpl is None:
374 if self._origpl is None:
375 self._origpl = self._pl
375 self._origpl = self._pl
376 self._map.setparents(p1, p2)
376 self._map.setparents(p1, p2)
377 copies = {}
377 copies = {}
378 if (
378 if (
379 oldp2 != self._nodeconstants.nullid
379 oldp2 != self._nodeconstants.nullid
380 and p2 == self._nodeconstants.nullid
380 and p2 == self._nodeconstants.nullid
381 ):
381 ):
382 candidatefiles = self._map.non_normal_or_other_parent_paths()
382 candidatefiles = self._map.non_normal_or_other_parent_paths()
383
383
384 for f in candidatefiles:
384 for f in candidatefiles:
385 s = self._map.get(f)
385 s = self._map.get(f)
386 if s is None:
386 if s is None:
387 continue
387 continue
388
388
389 # Discard "merged" markers when moving away from a merge state
389 # Discard "merged" markers when moving away from a merge state
390 if s.merged:
390 if s.merged:
391 source = self._map.copymap.get(f)
391 source = self._map.copymap.get(f)
392 if source:
392 if source:
393 copies[f] = source
393 copies[f] = source
394 self.normallookup(f)
394 self.normallookup(f)
395 # Also fix up otherparent markers
395 # Also fix up otherparent markers
396 elif s.state == b'n' and s.from_p2:
396 elif s.state == b'n' and s.from_p2:
397 source = self._map.copymap.get(f)
397 source = self._map.copymap.get(f)
398 if source:
398 if source:
399 copies[f] = source
399 copies[f] = source
400 self.add(f)
400 self.add(f)
401 return copies
401 return copies
402
402
403 def setbranch(self, branch):
403 def setbranch(self, branch):
404 self.__class__._branch.set(self, encoding.fromlocal(branch))
404 self.__class__._branch.set(self, encoding.fromlocal(branch))
405 f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
405 f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
406 try:
406 try:
407 f.write(self._branch + b'\n')
407 f.write(self._branch + b'\n')
408 f.close()
408 f.close()
409
409
410 # make sure filecache has the correct stat info for _branch after
410 # make sure filecache has the correct stat info for _branch after
411 # replacing the underlying file
411 # replacing the underlying file
412 ce = self._filecache[b'_branch']
412 ce = self._filecache[b'_branch']
413 if ce:
413 if ce:
414 ce.refresh()
414 ce.refresh()
415 except: # re-raises
415 except: # re-raises
416 f.discard()
416 f.discard()
417 raise
417 raise
418
418
419 def invalidate(self):
419 def invalidate(self):
420 """Causes the next access to reread the dirstate.
420 """Causes the next access to reread the dirstate.
421
421
422 This is different from localrepo.invalidatedirstate() because it always
422 This is different from localrepo.invalidatedirstate() because it always
423 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
423 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
424 check whether the dirstate has changed before rereading it."""
424 check whether the dirstate has changed before rereading it."""
425
425
426 for a in ("_map", "_branch", "_ignore"):
426 for a in ("_map", "_branch", "_ignore"):
427 if a in self.__dict__:
427 if a in self.__dict__:
428 delattr(self, a)
428 delattr(self, a)
429 self._lastnormaltime = 0
429 self._lastnormaltime = 0
430 self._dirty = False
430 self._dirty = False
431 self._updatedfiles.clear()
431 self._updatedfiles.clear()
432 self._parentwriters = 0
432 self._parentwriters = 0
433 self._origpl = None
433 self._origpl = None
434
434
435 def copy(self, source, dest):
435 def copy(self, source, dest):
436 """Mark dest as a copy of source. Unmark dest if source is None."""
436 """Mark dest as a copy of source. Unmark dest if source is None."""
437 if source == dest:
437 if source == dest:
438 return
438 return
439 self._dirty = True
439 self._dirty = True
440 if source is not None:
440 if source is not None:
441 self._map.copymap[dest] = source
441 self._map.copymap[dest] = source
442 self._updatedfiles.add(source)
442 self._updatedfiles.add(source)
443 self._updatedfiles.add(dest)
443 self._updatedfiles.add(dest)
444 elif self._map.copymap.pop(dest, None):
444 elif self._map.copymap.pop(dest, None):
445 self._updatedfiles.add(dest)
445 self._updatedfiles.add(dest)
446
446
447 def copied(self, file):
447 def copied(self, file):
448 return self._map.copymap.get(file, None)
448 return self._map.copymap.get(file, None)
449
449
450 def copies(self):
450 def copies(self):
451 return self._map.copymap
451 return self._map.copymap
452
452
453 def _addpath(
453 def _addpath(
454 self,
454 self,
455 f,
455 f,
456 state,
456 state,
457 mode,
457 mode,
458 size=NONNORMAL,
458 size=NONNORMAL,
459 mtime=AMBIGUOUS_TIME,
459 mtime=AMBIGUOUS_TIME,
460 from_p2=False,
460 from_p2=False,
461 possibly_dirty=False,
461 possibly_dirty=False,
462 ):
462 ):
463 oldstate = self[f]
463 oldstate = self[f]
464 if state == b'a' or oldstate == b'r':
464 if state == b'a' or oldstate == b'r':
465 scmutil.checkfilename(f)
465 scmutil.checkfilename(f)
466 if self._map.hastrackeddir(f):
466 if self._map.hastrackeddir(f):
467 msg = _(b'directory %r already in dirstate')
467 msg = _(b'directory %r already in dirstate')
468 msg %= pycompat.bytestr(f)
468 msg %= pycompat.bytestr(f)
469 raise error.Abort(msg)
469 raise error.Abort(msg)
470 # shadows
470 # shadows
471 for d in pathutil.finddirs(f):
471 for d in pathutil.finddirs(f):
472 if self._map.hastrackeddir(d):
472 if self._map.hastrackeddir(d):
473 break
473 break
474 entry = self._map.get(d)
474 entry = self._map.get(d)
475 if entry is not None and entry.state != b'r':
475 if entry is not None and not entry.removed:
476 msg = _(b'file %r in dirstate clashes with %r')
476 msg = _(b'file %r in dirstate clashes with %r')
477 msg %= (pycompat.bytestr(d), pycompat.bytestr(f))
477 msg %= (pycompat.bytestr(d), pycompat.bytestr(f))
478 raise error.Abort(msg)
478 raise error.Abort(msg)
479 if state == b'a':
479 if state == b'a':
480 assert not possibly_dirty
480 assert not possibly_dirty
481 assert not from_p2
481 assert not from_p2
482 size = NONNORMAL
482 size = NONNORMAL
483 mtime = AMBIGUOUS_TIME
483 mtime = AMBIGUOUS_TIME
484 elif from_p2:
484 elif from_p2:
485 assert not possibly_dirty
485 assert not possibly_dirty
486 size = FROM_P2
486 size = FROM_P2
487 mtime = AMBIGUOUS_TIME
487 mtime = AMBIGUOUS_TIME
488 elif possibly_dirty:
488 elif possibly_dirty:
489 mtime = AMBIGUOUS_TIME
489 mtime = AMBIGUOUS_TIME
490 else:
490 else:
491 assert size != FROM_P2
491 assert size != FROM_P2
492 assert size != NONNORMAL
492 assert size != NONNORMAL
493 size = size & _rangemask
493 size = size & _rangemask
494 mtime = mtime & _rangemask
494 mtime = mtime & _rangemask
495 self._dirty = True
495 self._dirty = True
496 self._updatedfiles.add(f)
496 self._updatedfiles.add(f)
497 self._map.addfile(f, oldstate, state, mode, size, mtime)
497 self._map.addfile(f, oldstate, state, mode, size, mtime)
498
498
499 def normal(self, f, parentfiledata=None):
499 def normal(self, f, parentfiledata=None):
500 """Mark a file normal and clean.
500 """Mark a file normal and clean.
501
501
502 parentfiledata: (mode, size, mtime) of the clean file
502 parentfiledata: (mode, size, mtime) of the clean file
503
503
504 parentfiledata should be computed from memory (for mode,
504 parentfiledata should be computed from memory (for mode,
505 size), as or close as possible from the point where we
505 size), as or close as possible from the point where we
506 determined the file was clean, to limit the risk of the
506 determined the file was clean, to limit the risk of the
507 file having been changed by an external process between the
507 file having been changed by an external process between the
508 moment where the file was determined to be clean and now."""
508 moment where the file was determined to be clean and now."""
509 if parentfiledata:
509 if parentfiledata:
510 (mode, size, mtime) = parentfiledata
510 (mode, size, mtime) = parentfiledata
511 else:
511 else:
512 s = os.lstat(self._join(f))
512 s = os.lstat(self._join(f))
513 mode = s.st_mode
513 mode = s.st_mode
514 size = s.st_size
514 size = s.st_size
515 mtime = s[stat.ST_MTIME]
515 mtime = s[stat.ST_MTIME]
516 self._addpath(f, b'n', mode, size, mtime)
516 self._addpath(f, b'n', mode, size, mtime)
517 self._map.copymap.pop(f, None)
517 self._map.copymap.pop(f, None)
518 if f in self._map.nonnormalset:
518 if f in self._map.nonnormalset:
519 self._map.nonnormalset.remove(f)
519 self._map.nonnormalset.remove(f)
520 if mtime > self._lastnormaltime:
520 if mtime > self._lastnormaltime:
521 # Remember the most recent modification timeslot for status(),
521 # Remember the most recent modification timeslot for status(),
522 # to make sure we won't miss future size-preserving file content
522 # to make sure we won't miss future size-preserving file content
523 # modifications that happen within the same timeslot.
523 # modifications that happen within the same timeslot.
524 self._lastnormaltime = mtime
524 self._lastnormaltime = mtime
525
525
526 def normallookup(self, f):
526 def normallookup(self, f):
527 '''Mark a file normal, but possibly dirty.'''
527 '''Mark a file normal, but possibly dirty.'''
528 if self.in_merge:
528 if self.in_merge:
529 # if there is a merge going on and the file was either
529 # if there is a merge going on and the file was either
530 # "merged" or coming from other parent (-2) before
530 # "merged" or coming from other parent (-2) before
531 # being removed, restore that state.
531 # being removed, restore that state.
532 entry = self._map.get(f)
532 entry = self._map.get(f)
533 if entry is not None:
533 if entry is not None:
534 if entry.state == b'r' and (
534 if entry.removed and (entry[2] == NONNORMAL or entry.from_p2):
535 entry[2] == NONNORMAL or entry.from_p2
536 ):
537 source = self._map.copymap.get(f)
535 source = self._map.copymap.get(f)
538 if entry[2] == NONNORMAL:
536 if entry[2] == NONNORMAL:
539 self.merge(f)
537 self.merge(f)
540 elif entry.from_p2:
538 elif entry.from_p2:
541 self.otherparent(f)
539 self.otherparent(f)
542 if source:
540 if source:
543 self.copy(source, f)
541 self.copy(source, f)
544 return
542 return
545 if entry.merged or entry.state == b'n' and entry.from_p2:
543 if entry.merged or entry.state == b'n' and entry.from_p2:
546 return
544 return
547 self._addpath(f, b'n', 0, possibly_dirty=True)
545 self._addpath(f, b'n', 0, possibly_dirty=True)
548 self._map.copymap.pop(f, None)
546 self._map.copymap.pop(f, None)
549
547
550 def otherparent(self, f):
548 def otherparent(self, f):
551 '''Mark as coming from the other parent, always dirty.'''
549 '''Mark as coming from the other parent, always dirty.'''
552 if not self.in_merge:
550 if not self.in_merge:
553 msg = _(b"setting %r to other parent only allowed in merges") % f
551 msg = _(b"setting %r to other parent only allowed in merges") % f
554 raise error.Abort(msg)
552 raise error.Abort(msg)
555 if f in self and self[f] == b'n':
553 if f in self and self[f] == b'n':
556 # merge-like
554 # merge-like
557 self._addpath(f, b'm', 0, from_p2=True)
555 self._addpath(f, b'm', 0, from_p2=True)
558 else:
556 else:
559 # add-like
557 # add-like
560 self._addpath(f, b'n', 0, from_p2=True)
558 self._addpath(f, b'n', 0, from_p2=True)
561 self._map.copymap.pop(f, None)
559 self._map.copymap.pop(f, None)
562
560
563 def add(self, f):
561 def add(self, f):
564 '''Mark a file added.'''
562 '''Mark a file added.'''
565 self._addpath(f, b'a', 0)
563 self._addpath(f, b'a', 0)
566 self._map.copymap.pop(f, None)
564 self._map.copymap.pop(f, None)
567
565
568 def remove(self, f):
566 def remove(self, f):
569 '''Mark a file removed.'''
567 '''Mark a file removed.'''
570 self._dirty = True
568 self._dirty = True
571 self._updatedfiles.add(f)
569 self._updatedfiles.add(f)
572 self._map.removefile(f, in_merge=self.in_merge)
570 self._map.removefile(f, in_merge=self.in_merge)
573
571
574 def merge(self, f):
572 def merge(self, f):
575 '''Mark a file merged.'''
573 '''Mark a file merged.'''
576 if not self.in_merge:
574 if not self.in_merge:
577 return self.normallookup(f)
575 return self.normallookup(f)
578 return self.otherparent(f)
576 return self.otherparent(f)
579
577
580 def drop(self, f):
578 def drop(self, f):
581 '''Drop a file from the dirstate'''
579 '''Drop a file from the dirstate'''
582 oldstate = self[f]
580 oldstate = self[f]
583 if self._map.dropfile(f, oldstate):
581 if self._map.dropfile(f, oldstate):
584 self._dirty = True
582 self._dirty = True
585 self._updatedfiles.add(f)
583 self._updatedfiles.add(f)
586 self._map.copymap.pop(f, None)
584 self._map.copymap.pop(f, None)
587
585
588 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
586 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
589 if exists is None:
587 if exists is None:
590 exists = os.path.lexists(os.path.join(self._root, path))
588 exists = os.path.lexists(os.path.join(self._root, path))
591 if not exists:
589 if not exists:
592 # Maybe a path component exists
590 # Maybe a path component exists
593 if not ignoremissing and b'/' in path:
591 if not ignoremissing and b'/' in path:
594 d, f = path.rsplit(b'/', 1)
592 d, f = path.rsplit(b'/', 1)
595 d = self._normalize(d, False, ignoremissing, None)
593 d = self._normalize(d, False, ignoremissing, None)
596 folded = d + b"/" + f
594 folded = d + b"/" + f
597 else:
595 else:
598 # No path components, preserve original case
596 # No path components, preserve original case
599 folded = path
597 folded = path
600 else:
598 else:
601 # recursively normalize leading directory components
599 # recursively normalize leading directory components
602 # against dirstate
600 # against dirstate
603 if b'/' in normed:
601 if b'/' in normed:
604 d, f = normed.rsplit(b'/', 1)
602 d, f = normed.rsplit(b'/', 1)
605 d = self._normalize(d, False, ignoremissing, True)
603 d = self._normalize(d, False, ignoremissing, True)
606 r = self._root + b"/" + d
604 r = self._root + b"/" + d
607 folded = d + b"/" + util.fspath(f, r)
605 folded = d + b"/" + util.fspath(f, r)
608 else:
606 else:
609 folded = util.fspath(normed, self._root)
607 folded = util.fspath(normed, self._root)
610 storemap[normed] = folded
608 storemap[normed] = folded
611
609
612 return folded
610 return folded
613
611
614 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
612 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
615 normed = util.normcase(path)
613 normed = util.normcase(path)
616 folded = self._map.filefoldmap.get(normed, None)
614 folded = self._map.filefoldmap.get(normed, None)
617 if folded is None:
615 if folded is None:
618 if isknown:
616 if isknown:
619 folded = path
617 folded = path
620 else:
618 else:
621 folded = self._discoverpath(
619 folded = self._discoverpath(
622 path, normed, ignoremissing, exists, self._map.filefoldmap
620 path, normed, ignoremissing, exists, self._map.filefoldmap
623 )
621 )
624 return folded
622 return folded
625
623
626 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
624 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
627 normed = util.normcase(path)
625 normed = util.normcase(path)
628 folded = self._map.filefoldmap.get(normed, None)
626 folded = self._map.filefoldmap.get(normed, None)
629 if folded is None:
627 if folded is None:
630 folded = self._map.dirfoldmap.get(normed, None)
628 folded = self._map.dirfoldmap.get(normed, None)
631 if folded is None:
629 if folded is None:
632 if isknown:
630 if isknown:
633 folded = path
631 folded = path
634 else:
632 else:
635 # store discovered result in dirfoldmap so that future
633 # store discovered result in dirfoldmap so that future
636 # normalizefile calls don't start matching directories
634 # normalizefile calls don't start matching directories
637 folded = self._discoverpath(
635 folded = self._discoverpath(
638 path, normed, ignoremissing, exists, self._map.dirfoldmap
636 path, normed, ignoremissing, exists, self._map.dirfoldmap
639 )
637 )
640 return folded
638 return folded
641
639
642 def normalize(self, path, isknown=False, ignoremissing=False):
640 def normalize(self, path, isknown=False, ignoremissing=False):
643 """
641 """
644 normalize the case of a pathname when on a casefolding filesystem
642 normalize the case of a pathname when on a casefolding filesystem
645
643
646 isknown specifies whether the filename came from walking the
644 isknown specifies whether the filename came from walking the
647 disk, to avoid extra filesystem access.
645 disk, to avoid extra filesystem access.
648
646
649 If ignoremissing is True, missing path are returned
647 If ignoremissing is True, missing path are returned
650 unchanged. Otherwise, we try harder to normalize possibly
648 unchanged. Otherwise, we try harder to normalize possibly
651 existing path components.
649 existing path components.
652
650
653 The normalized case is determined based on the following precedence:
651 The normalized case is determined based on the following precedence:
654
652
655 - version of name already stored in the dirstate
653 - version of name already stored in the dirstate
656 - version of name stored on disk
654 - version of name stored on disk
657 - version provided via command arguments
655 - version provided via command arguments
658 """
656 """
659
657
660 if self._checkcase:
658 if self._checkcase:
661 return self._normalize(path, isknown, ignoremissing)
659 return self._normalize(path, isknown, ignoremissing)
662 return path
660 return path
663
661
664 def clear(self):
662 def clear(self):
665 self._map.clear()
663 self._map.clear()
666 self._lastnormaltime = 0
664 self._lastnormaltime = 0
667 self._updatedfiles.clear()
665 self._updatedfiles.clear()
668 self._dirty = True
666 self._dirty = True
669
667
670 def rebuild(self, parent, allfiles, changedfiles=None):
668 def rebuild(self, parent, allfiles, changedfiles=None):
671 if changedfiles is None:
669 if changedfiles is None:
672 # Rebuild entire dirstate
670 # Rebuild entire dirstate
673 to_lookup = allfiles
671 to_lookup = allfiles
674 to_drop = []
672 to_drop = []
675 lastnormaltime = self._lastnormaltime
673 lastnormaltime = self._lastnormaltime
676 self.clear()
674 self.clear()
677 self._lastnormaltime = lastnormaltime
675 self._lastnormaltime = lastnormaltime
678 elif len(changedfiles) < 10:
676 elif len(changedfiles) < 10:
679 # Avoid turning allfiles into a set, which can be expensive if it's
677 # Avoid turning allfiles into a set, which can be expensive if it's
680 # large.
678 # large.
681 to_lookup = []
679 to_lookup = []
682 to_drop = []
680 to_drop = []
683 for f in changedfiles:
681 for f in changedfiles:
684 if f in allfiles:
682 if f in allfiles:
685 to_lookup.append(f)
683 to_lookup.append(f)
686 else:
684 else:
687 to_drop.append(f)
685 to_drop.append(f)
688 else:
686 else:
689 changedfilesset = set(changedfiles)
687 changedfilesset = set(changedfiles)
690 to_lookup = changedfilesset & set(allfiles)
688 to_lookup = changedfilesset & set(allfiles)
691 to_drop = changedfilesset - to_lookup
689 to_drop = changedfilesset - to_lookup
692
690
693 if self._origpl is None:
691 if self._origpl is None:
694 self._origpl = self._pl
692 self._origpl = self._pl
695 self._map.setparents(parent, self._nodeconstants.nullid)
693 self._map.setparents(parent, self._nodeconstants.nullid)
696
694
697 for f in to_lookup:
695 for f in to_lookup:
698 self.normallookup(f)
696 self.normallookup(f)
699 for f in to_drop:
697 for f in to_drop:
700 self.drop(f)
698 self.drop(f)
701
699
702 self._dirty = True
700 self._dirty = True
703
701
704 def identity(self):
702 def identity(self):
705 """Return identity of dirstate itself to detect changing in storage
703 """Return identity of dirstate itself to detect changing in storage
706
704
707 If identity of previous dirstate is equal to this, writing
705 If identity of previous dirstate is equal to this, writing
708 changes based on the former dirstate out can keep consistency.
706 changes based on the former dirstate out can keep consistency.
709 """
707 """
710 return self._map.identity
708 return self._map.identity
711
709
712 def write(self, tr):
710 def write(self, tr):
713 if not self._dirty:
711 if not self._dirty:
714 return
712 return
715
713
716 filename = self._filename
714 filename = self._filename
717 if tr:
715 if tr:
718 # 'dirstate.write()' is not only for writing in-memory
716 # 'dirstate.write()' is not only for writing in-memory
719 # changes out, but also for dropping ambiguous timestamp.
717 # changes out, but also for dropping ambiguous timestamp.
720 # delayed writing re-raise "ambiguous timestamp issue".
718 # delayed writing re-raise "ambiguous timestamp issue".
721 # See also the wiki page below for detail:
719 # See also the wiki page below for detail:
722 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
720 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
723
721
724 # emulate dropping timestamp in 'parsers.pack_dirstate'
722 # emulate dropping timestamp in 'parsers.pack_dirstate'
725 now = _getfsnow(self._opener)
723 now = _getfsnow(self._opener)
726 self._map.clearambiguoustimes(self._updatedfiles, now)
724 self._map.clearambiguoustimes(self._updatedfiles, now)
727
725
728 # emulate that all 'dirstate.normal' results are written out
726 # emulate that all 'dirstate.normal' results are written out
729 self._lastnormaltime = 0
727 self._lastnormaltime = 0
730 self._updatedfiles.clear()
728 self._updatedfiles.clear()
731
729
732 # delay writing in-memory changes out
730 # delay writing in-memory changes out
733 tr.addfilegenerator(
731 tr.addfilegenerator(
734 b'dirstate',
732 b'dirstate',
735 (self._filename,),
733 (self._filename,),
736 self._writedirstate,
734 self._writedirstate,
737 location=b'plain',
735 location=b'plain',
738 )
736 )
739 return
737 return
740
738
741 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
739 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
742 self._writedirstate(st)
740 self._writedirstate(st)
743
741
744 def addparentchangecallback(self, category, callback):
742 def addparentchangecallback(self, category, callback):
745 """add a callback to be called when the wd parents are changed
743 """add a callback to be called when the wd parents are changed
746
744
747 Callback will be called with the following arguments:
745 Callback will be called with the following arguments:
748 dirstate, (oldp1, oldp2), (newp1, newp2)
746 dirstate, (oldp1, oldp2), (newp1, newp2)
749
747
750 Category is a unique identifier to allow overwriting an old callback
748 Category is a unique identifier to allow overwriting an old callback
751 with a newer callback.
749 with a newer callback.
752 """
750 """
753 self._plchangecallbacks[category] = callback
751 self._plchangecallbacks[category] = callback
754
752
755 def _writedirstate(self, st):
753 def _writedirstate(self, st):
756 # notify callbacks about parents change
754 # notify callbacks about parents change
757 if self._origpl is not None and self._origpl != self._pl:
755 if self._origpl is not None and self._origpl != self._pl:
758 for c, callback in sorted(
756 for c, callback in sorted(
759 pycompat.iteritems(self._plchangecallbacks)
757 pycompat.iteritems(self._plchangecallbacks)
760 ):
758 ):
761 callback(self, self._origpl, self._pl)
759 callback(self, self._origpl, self._pl)
762 self._origpl = None
760 self._origpl = None
763 # use the modification time of the newly created temporary file as the
761 # use the modification time of the newly created temporary file as the
764 # filesystem's notion of 'now'
762 # filesystem's notion of 'now'
765 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
763 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
766
764
767 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
765 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
768 # timestamp of each entries in dirstate, because of 'now > mtime'
766 # timestamp of each entries in dirstate, because of 'now > mtime'
769 delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
767 delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
770 if delaywrite > 0:
768 if delaywrite > 0:
771 # do we have any files to delay for?
769 # do we have any files to delay for?
772 for f, e in pycompat.iteritems(self._map):
770 for f, e in pycompat.iteritems(self._map):
773 if e.state == b'n' and e[3] == now:
771 if e.state == b'n' and e[3] == now:
774 import time # to avoid useless import
772 import time # to avoid useless import
775
773
776 # rather than sleep n seconds, sleep until the next
774 # rather than sleep n seconds, sleep until the next
777 # multiple of n seconds
775 # multiple of n seconds
778 clock = time.time()
776 clock = time.time()
779 start = int(clock) - (int(clock) % delaywrite)
777 start = int(clock) - (int(clock) % delaywrite)
780 end = start + delaywrite
778 end = start + delaywrite
781 time.sleep(end - clock)
779 time.sleep(end - clock)
782 now = end # trust our estimate that the end is near now
780 now = end # trust our estimate that the end is near now
783 break
781 break
784
782
785 self._map.write(st, now)
783 self._map.write(st, now)
786 self._lastnormaltime = 0
784 self._lastnormaltime = 0
787 self._dirty = False
785 self._dirty = False
788
786
789 def _dirignore(self, f):
787 def _dirignore(self, f):
790 if self._ignore(f):
788 if self._ignore(f):
791 return True
789 return True
792 for p in pathutil.finddirs(f):
790 for p in pathutil.finddirs(f):
793 if self._ignore(p):
791 if self._ignore(p):
794 return True
792 return True
795 return False
793 return False
796
794
797 def _ignorefiles(self):
795 def _ignorefiles(self):
798 files = []
796 files = []
799 if os.path.exists(self._join(b'.hgignore')):
797 if os.path.exists(self._join(b'.hgignore')):
800 files.append(self._join(b'.hgignore'))
798 files.append(self._join(b'.hgignore'))
801 for name, path in self._ui.configitems(b"ui"):
799 for name, path in self._ui.configitems(b"ui"):
802 if name == b'ignore' or name.startswith(b'ignore.'):
800 if name == b'ignore' or name.startswith(b'ignore.'):
803 # we need to use os.path.join here rather than self._join
801 # we need to use os.path.join here rather than self._join
804 # because path is arbitrary and user-specified
802 # because path is arbitrary and user-specified
805 files.append(os.path.join(self._rootdir, util.expandpath(path)))
803 files.append(os.path.join(self._rootdir, util.expandpath(path)))
806 return files
804 return files
807
805
808 def _ignorefileandline(self, f):
806 def _ignorefileandline(self, f):
809 files = collections.deque(self._ignorefiles())
807 files = collections.deque(self._ignorefiles())
810 visited = set()
808 visited = set()
811 while files:
809 while files:
812 i = files.popleft()
810 i = files.popleft()
813 patterns = matchmod.readpatternfile(
811 patterns = matchmod.readpatternfile(
814 i, self._ui.warn, sourceinfo=True
812 i, self._ui.warn, sourceinfo=True
815 )
813 )
816 for pattern, lineno, line in patterns:
814 for pattern, lineno, line in patterns:
817 kind, p = matchmod._patsplit(pattern, b'glob')
815 kind, p = matchmod._patsplit(pattern, b'glob')
818 if kind == b"subinclude":
816 if kind == b"subinclude":
819 if p not in visited:
817 if p not in visited:
820 files.append(p)
818 files.append(p)
821 continue
819 continue
822 m = matchmod.match(
820 m = matchmod.match(
823 self._root, b'', [], [pattern], warn=self._ui.warn
821 self._root, b'', [], [pattern], warn=self._ui.warn
824 )
822 )
825 if m(f):
823 if m(f):
826 return (i, lineno, line)
824 return (i, lineno, line)
827 visited.add(i)
825 visited.add(i)
828 return (None, -1, b"")
826 return (None, -1, b"")
829
827
830 def _walkexplicit(self, match, subrepos):
828 def _walkexplicit(self, match, subrepos):
831 """Get stat data about the files explicitly specified by match.
829 """Get stat data about the files explicitly specified by match.
832
830
833 Return a triple (results, dirsfound, dirsnotfound).
831 Return a triple (results, dirsfound, dirsnotfound).
834 - results is a mapping from filename to stat result. It also contains
832 - results is a mapping from filename to stat result. It also contains
835 listings mapping subrepos and .hg to None.
833 listings mapping subrepos and .hg to None.
836 - dirsfound is a list of files found to be directories.
834 - dirsfound is a list of files found to be directories.
837 - dirsnotfound is a list of files that the dirstate thinks are
835 - dirsnotfound is a list of files that the dirstate thinks are
838 directories and that were not found."""
836 directories and that were not found."""
839
837
840 def badtype(mode):
838 def badtype(mode):
841 kind = _(b'unknown')
839 kind = _(b'unknown')
842 if stat.S_ISCHR(mode):
840 if stat.S_ISCHR(mode):
843 kind = _(b'character device')
841 kind = _(b'character device')
844 elif stat.S_ISBLK(mode):
842 elif stat.S_ISBLK(mode):
845 kind = _(b'block device')
843 kind = _(b'block device')
846 elif stat.S_ISFIFO(mode):
844 elif stat.S_ISFIFO(mode):
847 kind = _(b'fifo')
845 kind = _(b'fifo')
848 elif stat.S_ISSOCK(mode):
846 elif stat.S_ISSOCK(mode):
849 kind = _(b'socket')
847 kind = _(b'socket')
850 elif stat.S_ISDIR(mode):
848 elif stat.S_ISDIR(mode):
851 kind = _(b'directory')
849 kind = _(b'directory')
852 return _(b'unsupported file type (type is %s)') % kind
850 return _(b'unsupported file type (type is %s)') % kind
853
851
854 badfn = match.bad
852 badfn = match.bad
855 dmap = self._map
853 dmap = self._map
856 lstat = os.lstat
854 lstat = os.lstat
857 getkind = stat.S_IFMT
855 getkind = stat.S_IFMT
858 dirkind = stat.S_IFDIR
856 dirkind = stat.S_IFDIR
859 regkind = stat.S_IFREG
857 regkind = stat.S_IFREG
860 lnkkind = stat.S_IFLNK
858 lnkkind = stat.S_IFLNK
861 join = self._join
859 join = self._join
862 dirsfound = []
860 dirsfound = []
863 foundadd = dirsfound.append
861 foundadd = dirsfound.append
864 dirsnotfound = []
862 dirsnotfound = []
865 notfoundadd = dirsnotfound.append
863 notfoundadd = dirsnotfound.append
866
864
867 if not match.isexact() and self._checkcase:
865 if not match.isexact() and self._checkcase:
868 normalize = self._normalize
866 normalize = self._normalize
869 else:
867 else:
870 normalize = None
868 normalize = None
871
869
872 files = sorted(match.files())
870 files = sorted(match.files())
873 subrepos.sort()
871 subrepos.sort()
874 i, j = 0, 0
872 i, j = 0, 0
875 while i < len(files) and j < len(subrepos):
873 while i < len(files) and j < len(subrepos):
876 subpath = subrepos[j] + b"/"
874 subpath = subrepos[j] + b"/"
877 if files[i] < subpath:
875 if files[i] < subpath:
878 i += 1
876 i += 1
879 continue
877 continue
880 while i < len(files) and files[i].startswith(subpath):
878 while i < len(files) and files[i].startswith(subpath):
881 del files[i]
879 del files[i]
882 j += 1
880 j += 1
883
881
884 if not files or b'' in files:
882 if not files or b'' in files:
885 files = [b'']
883 files = [b'']
886 # constructing the foldmap is expensive, so don't do it for the
884 # constructing the foldmap is expensive, so don't do it for the
887 # common case where files is ['']
885 # common case where files is ['']
888 normalize = None
886 normalize = None
889 results = dict.fromkeys(subrepos)
887 results = dict.fromkeys(subrepos)
890 results[b'.hg'] = None
888 results[b'.hg'] = None
891
889
892 for ff in files:
890 for ff in files:
893 if normalize:
891 if normalize:
894 nf = normalize(ff, False, True)
892 nf = normalize(ff, False, True)
895 else:
893 else:
896 nf = ff
894 nf = ff
897 if nf in results:
895 if nf in results:
898 continue
896 continue
899
897
900 try:
898 try:
901 st = lstat(join(nf))
899 st = lstat(join(nf))
902 kind = getkind(st.st_mode)
900 kind = getkind(st.st_mode)
903 if kind == dirkind:
901 if kind == dirkind:
904 if nf in dmap:
902 if nf in dmap:
905 # file replaced by dir on disk but still in dirstate
903 # file replaced by dir on disk but still in dirstate
906 results[nf] = None
904 results[nf] = None
907 foundadd((nf, ff))
905 foundadd((nf, ff))
908 elif kind == regkind or kind == lnkkind:
906 elif kind == regkind or kind == lnkkind:
909 results[nf] = st
907 results[nf] = st
910 else:
908 else:
911 badfn(ff, badtype(kind))
909 badfn(ff, badtype(kind))
912 if nf in dmap:
910 if nf in dmap:
913 results[nf] = None
911 results[nf] = None
914 except OSError as inst: # nf not found on disk - it is dirstate only
912 except OSError as inst: # nf not found on disk - it is dirstate only
915 if nf in dmap: # does it exactly match a missing file?
913 if nf in dmap: # does it exactly match a missing file?
916 results[nf] = None
914 results[nf] = None
917 else: # does it match a missing directory?
915 else: # does it match a missing directory?
918 if self._map.hasdir(nf):
916 if self._map.hasdir(nf):
919 notfoundadd(nf)
917 notfoundadd(nf)
920 else:
918 else:
921 badfn(ff, encoding.strtolocal(inst.strerror))
919 badfn(ff, encoding.strtolocal(inst.strerror))
922
920
923 # match.files() may contain explicitly-specified paths that shouldn't
921 # match.files() may contain explicitly-specified paths that shouldn't
924 # be taken; drop them from the list of files found. dirsfound/notfound
922 # be taken; drop them from the list of files found. dirsfound/notfound
925 # aren't filtered here because they will be tested later.
923 # aren't filtered here because they will be tested later.
926 if match.anypats():
924 if match.anypats():
927 for f in list(results):
925 for f in list(results):
928 if f == b'.hg' or f in subrepos:
926 if f == b'.hg' or f in subrepos:
929 # keep sentinel to disable further out-of-repo walks
927 # keep sentinel to disable further out-of-repo walks
930 continue
928 continue
931 if not match(f):
929 if not match(f):
932 del results[f]
930 del results[f]
933
931
934 # Case insensitive filesystems cannot rely on lstat() failing to detect
932 # Case insensitive filesystems cannot rely on lstat() failing to detect
935 # a case-only rename. Prune the stat object for any file that does not
933 # a case-only rename. Prune the stat object for any file that does not
936 # match the case in the filesystem, if there are multiple files that
934 # match the case in the filesystem, if there are multiple files that
937 # normalize to the same path.
935 # normalize to the same path.
938 if match.isexact() and self._checkcase:
936 if match.isexact() and self._checkcase:
939 normed = {}
937 normed = {}
940
938
941 for f, st in pycompat.iteritems(results):
939 for f, st in pycompat.iteritems(results):
942 if st is None:
940 if st is None:
943 continue
941 continue
944
942
945 nc = util.normcase(f)
943 nc = util.normcase(f)
946 paths = normed.get(nc)
944 paths = normed.get(nc)
947
945
948 if paths is None:
946 if paths is None:
949 paths = set()
947 paths = set()
950 normed[nc] = paths
948 normed[nc] = paths
951
949
952 paths.add(f)
950 paths.add(f)
953
951
954 for norm, paths in pycompat.iteritems(normed):
952 for norm, paths in pycompat.iteritems(normed):
955 if len(paths) > 1:
953 if len(paths) > 1:
956 for path in paths:
954 for path in paths:
957 folded = self._discoverpath(
955 folded = self._discoverpath(
958 path, norm, True, None, self._map.dirfoldmap
956 path, norm, True, None, self._map.dirfoldmap
959 )
957 )
960 if path != folded:
958 if path != folded:
961 results[path] = None
959 results[path] = None
962
960
963 return results, dirsfound, dirsnotfound
961 return results, dirsfound, dirsnotfound
964
962
965 def walk(self, match, subrepos, unknown, ignored, full=True):
963 def walk(self, match, subrepos, unknown, ignored, full=True):
966 """
964 """
967 Walk recursively through the directory tree, finding all files
965 Walk recursively through the directory tree, finding all files
968 matched by match.
966 matched by match.
969
967
970 If full is False, maybe skip some known-clean files.
968 If full is False, maybe skip some known-clean files.
971
969
972 Return a dict mapping filename to stat-like object (either
970 Return a dict mapping filename to stat-like object (either
973 mercurial.osutil.stat instance or return value of os.stat()).
971 mercurial.osutil.stat instance or return value of os.stat()).
974
972
975 """
973 """
976 # full is a flag that extensions that hook into walk can use -- this
974 # full is a flag that extensions that hook into walk can use -- this
977 # implementation doesn't use it at all. This satisfies the contract
975 # implementation doesn't use it at all. This satisfies the contract
978 # because we only guarantee a "maybe".
976 # because we only guarantee a "maybe".
979
977
980 if ignored:
978 if ignored:
981 ignore = util.never
979 ignore = util.never
982 dirignore = util.never
980 dirignore = util.never
983 elif unknown:
981 elif unknown:
984 ignore = self._ignore
982 ignore = self._ignore
985 dirignore = self._dirignore
983 dirignore = self._dirignore
986 else:
984 else:
987 # if not unknown and not ignored, drop dir recursion and step 2
985 # if not unknown and not ignored, drop dir recursion and step 2
988 ignore = util.always
986 ignore = util.always
989 dirignore = util.always
987 dirignore = util.always
990
988
991 matchfn = match.matchfn
989 matchfn = match.matchfn
992 matchalways = match.always()
990 matchalways = match.always()
993 matchtdir = match.traversedir
991 matchtdir = match.traversedir
994 dmap = self._map
992 dmap = self._map
995 listdir = util.listdir
993 listdir = util.listdir
996 lstat = os.lstat
994 lstat = os.lstat
997 dirkind = stat.S_IFDIR
995 dirkind = stat.S_IFDIR
998 regkind = stat.S_IFREG
996 regkind = stat.S_IFREG
999 lnkkind = stat.S_IFLNK
997 lnkkind = stat.S_IFLNK
1000 join = self._join
998 join = self._join
1001
999
1002 exact = skipstep3 = False
1000 exact = skipstep3 = False
1003 if match.isexact(): # match.exact
1001 if match.isexact(): # match.exact
1004 exact = True
1002 exact = True
1005 dirignore = util.always # skip step 2
1003 dirignore = util.always # skip step 2
1006 elif match.prefix(): # match.match, no patterns
1004 elif match.prefix(): # match.match, no patterns
1007 skipstep3 = True
1005 skipstep3 = True
1008
1006
1009 if not exact and self._checkcase:
1007 if not exact and self._checkcase:
1010 normalize = self._normalize
1008 normalize = self._normalize
1011 normalizefile = self._normalizefile
1009 normalizefile = self._normalizefile
1012 skipstep3 = False
1010 skipstep3 = False
1013 else:
1011 else:
1014 normalize = self._normalize
1012 normalize = self._normalize
1015 normalizefile = None
1013 normalizefile = None
1016
1014
1017 # step 1: find all explicit files
1015 # step 1: find all explicit files
1018 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1016 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1019 if matchtdir:
1017 if matchtdir:
1020 for d in work:
1018 for d in work:
1021 matchtdir(d[0])
1019 matchtdir(d[0])
1022 for d in dirsnotfound:
1020 for d in dirsnotfound:
1023 matchtdir(d)
1021 matchtdir(d)
1024
1022
1025 skipstep3 = skipstep3 and not (work or dirsnotfound)
1023 skipstep3 = skipstep3 and not (work or dirsnotfound)
1026 work = [d for d in work if not dirignore(d[0])]
1024 work = [d for d in work if not dirignore(d[0])]
1027
1025
1028 # step 2: visit subdirectories
1026 # step 2: visit subdirectories
1029 def traverse(work, alreadynormed):
1027 def traverse(work, alreadynormed):
1030 wadd = work.append
1028 wadd = work.append
1031 while work:
1029 while work:
1032 tracing.counter('dirstate.walk work', len(work))
1030 tracing.counter('dirstate.walk work', len(work))
1033 nd = work.pop()
1031 nd = work.pop()
1034 visitentries = match.visitchildrenset(nd)
1032 visitentries = match.visitchildrenset(nd)
1035 if not visitentries:
1033 if not visitentries:
1036 continue
1034 continue
1037 if visitentries == b'this' or visitentries == b'all':
1035 if visitentries == b'this' or visitentries == b'all':
1038 visitentries = None
1036 visitentries = None
1039 skip = None
1037 skip = None
1040 if nd != b'':
1038 if nd != b'':
1041 skip = b'.hg'
1039 skip = b'.hg'
1042 try:
1040 try:
1043 with tracing.log('dirstate.walk.traverse listdir %s', nd):
1041 with tracing.log('dirstate.walk.traverse listdir %s', nd):
1044 entries = listdir(join(nd), stat=True, skip=skip)
1042 entries = listdir(join(nd), stat=True, skip=skip)
1045 except OSError as inst:
1043 except OSError as inst:
1046 if inst.errno in (errno.EACCES, errno.ENOENT):
1044 if inst.errno in (errno.EACCES, errno.ENOENT):
1047 match.bad(
1045 match.bad(
1048 self.pathto(nd), encoding.strtolocal(inst.strerror)
1046 self.pathto(nd), encoding.strtolocal(inst.strerror)
1049 )
1047 )
1050 continue
1048 continue
1051 raise
1049 raise
1052 for f, kind, st in entries:
1050 for f, kind, st in entries:
1053 # Some matchers may return files in the visitentries set,
1051 # Some matchers may return files in the visitentries set,
1054 # instead of 'this', if the matcher explicitly mentions them
1052 # instead of 'this', if the matcher explicitly mentions them
1055 # and is not an exactmatcher. This is acceptable; we do not
1053 # and is not an exactmatcher. This is acceptable; we do not
1056 # make any hard assumptions about file-or-directory below
1054 # make any hard assumptions about file-or-directory below
1057 # based on the presence of `f` in visitentries. If
1055 # based on the presence of `f` in visitentries. If
1058 # visitchildrenset returned a set, we can always skip the
1056 # visitchildrenset returned a set, we can always skip the
1059 # entries *not* in the set it provided regardless of whether
1057 # entries *not* in the set it provided regardless of whether
1060 # they're actually a file or a directory.
1058 # they're actually a file or a directory.
1061 if visitentries and f not in visitentries:
1059 if visitentries and f not in visitentries:
1062 continue
1060 continue
1063 if normalizefile:
1061 if normalizefile:
1064 # even though f might be a directory, we're only
1062 # even though f might be a directory, we're only
1065 # interested in comparing it to files currently in the
1063 # interested in comparing it to files currently in the
1066 # dmap -- therefore normalizefile is enough
1064 # dmap -- therefore normalizefile is enough
1067 nf = normalizefile(
1065 nf = normalizefile(
1068 nd and (nd + b"/" + f) or f, True, True
1066 nd and (nd + b"/" + f) or f, True, True
1069 )
1067 )
1070 else:
1068 else:
1071 nf = nd and (nd + b"/" + f) or f
1069 nf = nd and (nd + b"/" + f) or f
1072 if nf not in results:
1070 if nf not in results:
1073 if kind == dirkind:
1071 if kind == dirkind:
1074 if not ignore(nf):
1072 if not ignore(nf):
1075 if matchtdir:
1073 if matchtdir:
1076 matchtdir(nf)
1074 matchtdir(nf)
1077 wadd(nf)
1075 wadd(nf)
1078 if nf in dmap and (matchalways or matchfn(nf)):
1076 if nf in dmap and (matchalways or matchfn(nf)):
1079 results[nf] = None
1077 results[nf] = None
1080 elif kind == regkind or kind == lnkkind:
1078 elif kind == regkind or kind == lnkkind:
1081 if nf in dmap:
1079 if nf in dmap:
1082 if matchalways or matchfn(nf):
1080 if matchalways or matchfn(nf):
1083 results[nf] = st
1081 results[nf] = st
1084 elif (matchalways or matchfn(nf)) and not ignore(
1082 elif (matchalways or matchfn(nf)) and not ignore(
1085 nf
1083 nf
1086 ):
1084 ):
1087 # unknown file -- normalize if necessary
1085 # unknown file -- normalize if necessary
1088 if not alreadynormed:
1086 if not alreadynormed:
1089 nf = normalize(nf, False, True)
1087 nf = normalize(nf, False, True)
1090 results[nf] = st
1088 results[nf] = st
1091 elif nf in dmap and (matchalways or matchfn(nf)):
1089 elif nf in dmap and (matchalways or matchfn(nf)):
1092 results[nf] = None
1090 results[nf] = None
1093
1091
1094 for nd, d in work:
1092 for nd, d in work:
1095 # alreadynormed means that processwork doesn't have to do any
1093 # alreadynormed means that processwork doesn't have to do any
1096 # expensive directory normalization
1094 # expensive directory normalization
1097 alreadynormed = not normalize or nd == d
1095 alreadynormed = not normalize or nd == d
1098 traverse([d], alreadynormed)
1096 traverse([d], alreadynormed)
1099
1097
1100 for s in subrepos:
1098 for s in subrepos:
1101 del results[s]
1099 del results[s]
1102 del results[b'.hg']
1100 del results[b'.hg']
1103
1101
1104 # step 3: visit remaining files from dmap
1102 # step 3: visit remaining files from dmap
1105 if not skipstep3 and not exact:
1103 if not skipstep3 and not exact:
1106 # If a dmap file is not in results yet, it was either
1104 # If a dmap file is not in results yet, it was either
1107 # a) not matching matchfn b) ignored, c) missing, or d) under a
1105 # a) not matching matchfn b) ignored, c) missing, or d) under a
1108 # symlink directory.
1106 # symlink directory.
1109 if not results and matchalways:
1107 if not results and matchalways:
1110 visit = [f for f in dmap]
1108 visit = [f for f in dmap]
1111 else:
1109 else:
1112 visit = [f for f in dmap if f not in results and matchfn(f)]
1110 visit = [f for f in dmap if f not in results and matchfn(f)]
1113 visit.sort()
1111 visit.sort()
1114
1112
1115 if unknown:
1113 if unknown:
1116 # unknown == True means we walked all dirs under the roots
1114 # unknown == True means we walked all dirs under the roots
1117 # that wasn't ignored, and everything that matched was stat'ed
1115 # that wasn't ignored, and everything that matched was stat'ed
1118 # and is already in results.
1116 # and is already in results.
1119 # The rest must thus be ignored or under a symlink.
1117 # The rest must thus be ignored or under a symlink.
1120 audit_path = pathutil.pathauditor(self._root, cached=True)
1118 audit_path = pathutil.pathauditor(self._root, cached=True)
1121
1119
1122 for nf in iter(visit):
1120 for nf in iter(visit):
1123 # If a stat for the same file was already added with a
1121 # If a stat for the same file was already added with a
1124 # different case, don't add one for this, since that would
1122 # different case, don't add one for this, since that would
1125 # make it appear as if the file exists under both names
1123 # make it appear as if the file exists under both names
1126 # on disk.
1124 # on disk.
1127 if (
1125 if (
1128 normalizefile
1126 normalizefile
1129 and normalizefile(nf, True, True) in results
1127 and normalizefile(nf, True, True) in results
1130 ):
1128 ):
1131 results[nf] = None
1129 results[nf] = None
1132 # Report ignored items in the dmap as long as they are not
1130 # Report ignored items in the dmap as long as they are not
1133 # under a symlink directory.
1131 # under a symlink directory.
1134 elif audit_path.check(nf):
1132 elif audit_path.check(nf):
1135 try:
1133 try:
1136 results[nf] = lstat(join(nf))
1134 results[nf] = lstat(join(nf))
1137 # file was just ignored, no links, and exists
1135 # file was just ignored, no links, and exists
1138 except OSError:
1136 except OSError:
1139 # file doesn't exist
1137 # file doesn't exist
1140 results[nf] = None
1138 results[nf] = None
1141 else:
1139 else:
1142 # It's either missing or under a symlink directory
1140 # It's either missing or under a symlink directory
1143 # which we in this case report as missing
1141 # which we in this case report as missing
1144 results[nf] = None
1142 results[nf] = None
1145 else:
1143 else:
1146 # We may not have walked the full directory tree above,
1144 # We may not have walked the full directory tree above,
1147 # so stat and check everything we missed.
1145 # so stat and check everything we missed.
1148 iv = iter(visit)
1146 iv = iter(visit)
1149 for st in util.statfiles([join(i) for i in visit]):
1147 for st in util.statfiles([join(i) for i in visit]):
1150 results[next(iv)] = st
1148 results[next(iv)] = st
1151 return results
1149 return results
1152
1150
1153 def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
1151 def _rust_status(self, matcher, list_clean, list_ignored, list_unknown):
1154 # Force Rayon (Rust parallelism library) to respect the number of
1152 # Force Rayon (Rust parallelism library) to respect the number of
1155 # workers. This is a temporary workaround until Rust code knows
1153 # workers. This is a temporary workaround until Rust code knows
1156 # how to read the config file.
1154 # how to read the config file.
1157 numcpus = self._ui.configint(b"worker", b"numcpus")
1155 numcpus = self._ui.configint(b"worker", b"numcpus")
1158 if numcpus is not None:
1156 if numcpus is not None:
1159 encoding.environ.setdefault(b'RAYON_NUM_THREADS', b'%d' % numcpus)
1157 encoding.environ.setdefault(b'RAYON_NUM_THREADS', b'%d' % numcpus)
1160
1158
1161 workers_enabled = self._ui.configbool(b"worker", b"enabled", True)
1159 workers_enabled = self._ui.configbool(b"worker", b"enabled", True)
1162 if not workers_enabled:
1160 if not workers_enabled:
1163 encoding.environ[b"RAYON_NUM_THREADS"] = b"1"
1161 encoding.environ[b"RAYON_NUM_THREADS"] = b"1"
1164
1162
1165 (
1163 (
1166 lookup,
1164 lookup,
1167 modified,
1165 modified,
1168 added,
1166 added,
1169 removed,
1167 removed,
1170 deleted,
1168 deleted,
1171 clean,
1169 clean,
1172 ignored,
1170 ignored,
1173 unknown,
1171 unknown,
1174 warnings,
1172 warnings,
1175 bad,
1173 bad,
1176 traversed,
1174 traversed,
1177 dirty,
1175 dirty,
1178 ) = rustmod.status(
1176 ) = rustmod.status(
1179 self._map._rustmap,
1177 self._map._rustmap,
1180 matcher,
1178 matcher,
1181 self._rootdir,
1179 self._rootdir,
1182 self._ignorefiles(),
1180 self._ignorefiles(),
1183 self._checkexec,
1181 self._checkexec,
1184 self._lastnormaltime,
1182 self._lastnormaltime,
1185 bool(list_clean),
1183 bool(list_clean),
1186 bool(list_ignored),
1184 bool(list_ignored),
1187 bool(list_unknown),
1185 bool(list_unknown),
1188 bool(matcher.traversedir),
1186 bool(matcher.traversedir),
1189 )
1187 )
1190
1188
1191 self._dirty |= dirty
1189 self._dirty |= dirty
1192
1190
1193 if matcher.traversedir:
1191 if matcher.traversedir:
1194 for dir in traversed:
1192 for dir in traversed:
1195 matcher.traversedir(dir)
1193 matcher.traversedir(dir)
1196
1194
1197 if self._ui.warn:
1195 if self._ui.warn:
1198 for item in warnings:
1196 for item in warnings:
1199 if isinstance(item, tuple):
1197 if isinstance(item, tuple):
1200 file_path, syntax = item
1198 file_path, syntax = item
1201 msg = _(b"%s: ignoring invalid syntax '%s'\n") % (
1199 msg = _(b"%s: ignoring invalid syntax '%s'\n") % (
1202 file_path,
1200 file_path,
1203 syntax,
1201 syntax,
1204 )
1202 )
1205 self._ui.warn(msg)
1203 self._ui.warn(msg)
1206 else:
1204 else:
1207 msg = _(b"skipping unreadable pattern file '%s': %s\n")
1205 msg = _(b"skipping unreadable pattern file '%s': %s\n")
1208 self._ui.warn(
1206 self._ui.warn(
1209 msg
1207 msg
1210 % (
1208 % (
1211 pathutil.canonpath(
1209 pathutil.canonpath(
1212 self._rootdir, self._rootdir, item
1210 self._rootdir, self._rootdir, item
1213 ),
1211 ),
1214 b"No such file or directory",
1212 b"No such file or directory",
1215 )
1213 )
1216 )
1214 )
1217
1215
1218 for (fn, message) in bad:
1216 for (fn, message) in bad:
1219 matcher.bad(fn, encoding.strtolocal(message))
1217 matcher.bad(fn, encoding.strtolocal(message))
1220
1218
1221 status = scmutil.status(
1219 status = scmutil.status(
1222 modified=modified,
1220 modified=modified,
1223 added=added,
1221 added=added,
1224 removed=removed,
1222 removed=removed,
1225 deleted=deleted,
1223 deleted=deleted,
1226 unknown=unknown,
1224 unknown=unknown,
1227 ignored=ignored,
1225 ignored=ignored,
1228 clean=clean,
1226 clean=clean,
1229 )
1227 )
1230 return (lookup, status)
1228 return (lookup, status)
1231
1229
1232 def status(self, match, subrepos, ignored, clean, unknown):
1230 def status(self, match, subrepos, ignored, clean, unknown):
1233 """Determine the status of the working copy relative to the
1231 """Determine the status of the working copy relative to the
1234 dirstate and return a pair of (unsure, status), where status is of type
1232 dirstate and return a pair of (unsure, status), where status is of type
1235 scmutil.status and:
1233 scmutil.status and:
1236
1234
1237 unsure:
1235 unsure:
1238 files that might have been modified since the dirstate was
1236 files that might have been modified since the dirstate was
1239 written, but need to be read to be sure (size is the same
1237 written, but need to be read to be sure (size is the same
1240 but mtime differs)
1238 but mtime differs)
1241 status.modified:
1239 status.modified:
1242 files that have definitely been modified since the dirstate
1240 files that have definitely been modified since the dirstate
1243 was written (different size or mode)
1241 was written (different size or mode)
1244 status.clean:
1242 status.clean:
1245 files that have definitely not been modified since the
1243 files that have definitely not been modified since the
1246 dirstate was written
1244 dirstate was written
1247 """
1245 """
1248 listignored, listclean, listunknown = ignored, clean, unknown
1246 listignored, listclean, listunknown = ignored, clean, unknown
1249 lookup, modified, added, unknown, ignored = [], [], [], [], []
1247 lookup, modified, added, unknown, ignored = [], [], [], [], []
1250 removed, deleted, clean = [], [], []
1248 removed, deleted, clean = [], [], []
1251
1249
1252 dmap = self._map
1250 dmap = self._map
1253 dmap.preload()
1251 dmap.preload()
1254
1252
1255 use_rust = True
1253 use_rust = True
1256
1254
1257 allowed_matchers = (
1255 allowed_matchers = (
1258 matchmod.alwaysmatcher,
1256 matchmod.alwaysmatcher,
1259 matchmod.exactmatcher,
1257 matchmod.exactmatcher,
1260 matchmod.includematcher,
1258 matchmod.includematcher,
1261 )
1259 )
1262
1260
1263 if rustmod is None:
1261 if rustmod is None:
1264 use_rust = False
1262 use_rust = False
1265 elif self._checkcase:
1263 elif self._checkcase:
1266 # Case-insensitive filesystems are not handled yet
1264 # Case-insensitive filesystems are not handled yet
1267 use_rust = False
1265 use_rust = False
1268 elif subrepos:
1266 elif subrepos:
1269 use_rust = False
1267 use_rust = False
1270 elif sparse.enabled:
1268 elif sparse.enabled:
1271 use_rust = False
1269 use_rust = False
1272 elif not isinstance(match, allowed_matchers):
1270 elif not isinstance(match, allowed_matchers):
1273 # Some matchers have yet to be implemented
1271 # Some matchers have yet to be implemented
1274 use_rust = False
1272 use_rust = False
1275
1273
1276 if use_rust:
1274 if use_rust:
1277 try:
1275 try:
1278 return self._rust_status(
1276 return self._rust_status(
1279 match, listclean, listignored, listunknown
1277 match, listclean, listignored, listunknown
1280 )
1278 )
1281 except rustmod.FallbackError:
1279 except rustmod.FallbackError:
1282 pass
1280 pass
1283
1281
1284 def noop(f):
1282 def noop(f):
1285 pass
1283 pass
1286
1284
1287 dcontains = dmap.__contains__
1285 dcontains = dmap.__contains__
1288 dget = dmap.__getitem__
1286 dget = dmap.__getitem__
1289 ladd = lookup.append # aka "unsure"
1287 ladd = lookup.append # aka "unsure"
1290 madd = modified.append
1288 madd = modified.append
1291 aadd = added.append
1289 aadd = added.append
1292 uadd = unknown.append if listunknown else noop
1290 uadd = unknown.append if listunknown else noop
1293 iadd = ignored.append if listignored else noop
1291 iadd = ignored.append if listignored else noop
1294 radd = removed.append
1292 radd = removed.append
1295 dadd = deleted.append
1293 dadd = deleted.append
1296 cadd = clean.append if listclean else noop
1294 cadd = clean.append if listclean else noop
1297 mexact = match.exact
1295 mexact = match.exact
1298 dirignore = self._dirignore
1296 dirignore = self._dirignore
1299 checkexec = self._checkexec
1297 checkexec = self._checkexec
1300 copymap = self._map.copymap
1298 copymap = self._map.copymap
1301 lastnormaltime = self._lastnormaltime
1299 lastnormaltime = self._lastnormaltime
1302
1300
1303 # We need to do full walks when either
1301 # We need to do full walks when either
1304 # - we're listing all clean files, or
1302 # - we're listing all clean files, or
1305 # - match.traversedir does something, because match.traversedir should
1303 # - match.traversedir does something, because match.traversedir should
1306 # be called for every dir in the working dir
1304 # be called for every dir in the working dir
1307 full = listclean or match.traversedir is not None
1305 full = listclean or match.traversedir is not None
1308 for fn, st in pycompat.iteritems(
1306 for fn, st in pycompat.iteritems(
1309 self.walk(match, subrepos, listunknown, listignored, full=full)
1307 self.walk(match, subrepos, listunknown, listignored, full=full)
1310 ):
1308 ):
1311 if not dcontains(fn):
1309 if not dcontains(fn):
1312 if (listignored or mexact(fn)) and dirignore(fn):
1310 if (listignored or mexact(fn)) and dirignore(fn):
1313 if listignored:
1311 if listignored:
1314 iadd(fn)
1312 iadd(fn)
1315 else:
1313 else:
1316 uadd(fn)
1314 uadd(fn)
1317 continue
1315 continue
1318
1316
1319 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1317 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1320 # written like that for performance reasons. dmap[fn] is not a
1318 # written like that for performance reasons. dmap[fn] is not a
1321 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1319 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1322 # opcode has fast paths when the value to be unpacked is a tuple or
1320 # opcode has fast paths when the value to be unpacked is a tuple or
1323 # a list, but falls back to creating a full-fledged iterator in
1321 # a list, but falls back to creating a full-fledged iterator in
1324 # general. That is much slower than simply accessing and storing the
1322 # general. That is much slower than simply accessing and storing the
1325 # tuple members one by one.
1323 # tuple members one by one.
1326 t = dget(fn)
1324 t = dget(fn)
1327 state = t.state
1325 state = t.state
1328 mode = t[1]
1326 mode = t[1]
1329 size = t[2]
1327 size = t[2]
1330 time = t[3]
1328 time = t[3]
1331
1329
1332 if not st and state in b"nma":
1330 if not st and state in b"nma":
1333 dadd(fn)
1331 dadd(fn)
1334 elif state == b'n':
1332 elif state == b'n':
1335 if (
1333 if (
1336 size >= 0
1334 size >= 0
1337 and (
1335 and (
1338 (size != st.st_size and size != st.st_size & _rangemask)
1336 (size != st.st_size and size != st.st_size & _rangemask)
1339 or ((mode ^ st.st_mode) & 0o100 and checkexec)
1337 or ((mode ^ st.st_mode) & 0o100 and checkexec)
1340 )
1338 )
1341 or t.from_p2
1339 or t.from_p2
1342 or fn in copymap
1340 or fn in copymap
1343 ):
1341 ):
1344 if stat.S_ISLNK(st.st_mode) and size != st.st_size:
1342 if stat.S_ISLNK(st.st_mode) and size != st.st_size:
1345 # issue6456: Size returned may be longer due to
1343 # issue6456: Size returned may be longer due to
1346 # encryption on EXT-4 fscrypt, undecided.
1344 # encryption on EXT-4 fscrypt, undecided.
1347 ladd(fn)
1345 ladd(fn)
1348 else:
1346 else:
1349 madd(fn)
1347 madd(fn)
1350 elif (
1348 elif (
1351 time != st[stat.ST_MTIME]
1349 time != st[stat.ST_MTIME]
1352 and time != st[stat.ST_MTIME] & _rangemask
1350 and time != st[stat.ST_MTIME] & _rangemask
1353 ):
1351 ):
1354 ladd(fn)
1352 ladd(fn)
1355 elif st[stat.ST_MTIME] == lastnormaltime:
1353 elif st[stat.ST_MTIME] == lastnormaltime:
1356 # fn may have just been marked as normal and it may have
1354 # fn may have just been marked as normal and it may have
1357 # changed in the same second without changing its size.
1355 # changed in the same second without changing its size.
1358 # This can happen if we quickly do multiple commits.
1356 # This can happen if we quickly do multiple commits.
1359 # Force lookup, so we don't miss such a racy file change.
1357 # Force lookup, so we don't miss such a racy file change.
1360 ladd(fn)
1358 ladd(fn)
1361 elif listclean:
1359 elif listclean:
1362 cadd(fn)
1360 cadd(fn)
1363 elif t.merged:
1361 elif t.merged:
1364 madd(fn)
1362 madd(fn)
1365 elif state == b'a':
1363 elif state == b'a':
1366 aadd(fn)
1364 aadd(fn)
1367 elif state == b'r':
1365 elif t.removed:
1368 radd(fn)
1366 radd(fn)
1369 status = scmutil.status(
1367 status = scmutil.status(
1370 modified, added, removed, deleted, unknown, ignored, clean
1368 modified, added, removed, deleted, unknown, ignored, clean
1371 )
1369 )
1372 return (lookup, status)
1370 return (lookup, status)
1373
1371
1374 def matches(self, match):
1372 def matches(self, match):
1375 """
1373 """
1376 return files in the dirstate (in whatever state) filtered by match
1374 return files in the dirstate (in whatever state) filtered by match
1377 """
1375 """
1378 dmap = self._map
1376 dmap = self._map
1379 if rustmod is not None:
1377 if rustmod is not None:
1380 dmap = self._map._rustmap
1378 dmap = self._map._rustmap
1381
1379
1382 if match.always():
1380 if match.always():
1383 return dmap.keys()
1381 return dmap.keys()
1384 files = match.files()
1382 files = match.files()
1385 if match.isexact():
1383 if match.isexact():
1386 # fast path -- filter the other way around, since typically files is
1384 # fast path -- filter the other way around, since typically files is
1387 # much smaller than dmap
1385 # much smaller than dmap
1388 return [f for f in files if f in dmap]
1386 return [f for f in files if f in dmap]
1389 if match.prefix() and all(fn in dmap for fn in files):
1387 if match.prefix() and all(fn in dmap for fn in files):
1390 # fast path -- all the values are known to be files, so just return
1388 # fast path -- all the values are known to be files, so just return
1391 # that
1389 # that
1392 return list(files)
1390 return list(files)
1393 return [f for f in dmap if match(f)]
1391 return [f for f in dmap if match(f)]
1394
1392
1395 def _actualfilename(self, tr):
1393 def _actualfilename(self, tr):
1396 if tr:
1394 if tr:
1397 return self._pendingfilename
1395 return self._pendingfilename
1398 else:
1396 else:
1399 return self._filename
1397 return self._filename
1400
1398
1401 def savebackup(self, tr, backupname):
1399 def savebackup(self, tr, backupname):
1402 '''Save current dirstate into backup file'''
1400 '''Save current dirstate into backup file'''
1403 filename = self._actualfilename(tr)
1401 filename = self._actualfilename(tr)
1404 assert backupname != filename
1402 assert backupname != filename
1405
1403
1406 # use '_writedirstate' instead of 'write' to write changes certainly,
1404 # use '_writedirstate' instead of 'write' to write changes certainly,
1407 # because the latter omits writing out if transaction is running.
1405 # because the latter omits writing out if transaction is running.
1408 # output file will be used to create backup of dirstate at this point.
1406 # output file will be used to create backup of dirstate at this point.
1409 if self._dirty or not self._opener.exists(filename):
1407 if self._dirty or not self._opener.exists(filename):
1410 self._writedirstate(
1408 self._writedirstate(
1411 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1409 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1412 )
1410 )
1413
1411
1414 if tr:
1412 if tr:
1415 # ensure that subsequent tr.writepending returns True for
1413 # ensure that subsequent tr.writepending returns True for
1416 # changes written out above, even if dirstate is never
1414 # changes written out above, even if dirstate is never
1417 # changed after this
1415 # changed after this
1418 tr.addfilegenerator(
1416 tr.addfilegenerator(
1419 b'dirstate',
1417 b'dirstate',
1420 (self._filename,),
1418 (self._filename,),
1421 self._writedirstate,
1419 self._writedirstate,
1422 location=b'plain',
1420 location=b'plain',
1423 )
1421 )
1424
1422
1425 # ensure that pending file written above is unlinked at
1423 # ensure that pending file written above is unlinked at
1426 # failure, even if tr.writepending isn't invoked until the
1424 # failure, even if tr.writepending isn't invoked until the
1427 # end of this transaction
1425 # end of this transaction
1428 tr.registertmp(filename, location=b'plain')
1426 tr.registertmp(filename, location=b'plain')
1429
1427
1430 self._opener.tryunlink(backupname)
1428 self._opener.tryunlink(backupname)
1431 # hardlink backup is okay because _writedirstate is always called
1429 # hardlink backup is okay because _writedirstate is always called
1432 # with an "atomictemp=True" file.
1430 # with an "atomictemp=True" file.
1433 util.copyfile(
1431 util.copyfile(
1434 self._opener.join(filename),
1432 self._opener.join(filename),
1435 self._opener.join(backupname),
1433 self._opener.join(backupname),
1436 hardlink=True,
1434 hardlink=True,
1437 )
1435 )
1438
1436
1439 def restorebackup(self, tr, backupname):
1437 def restorebackup(self, tr, backupname):
1440 '''Restore dirstate by backup file'''
1438 '''Restore dirstate by backup file'''
1441 # this "invalidate()" prevents "wlock.release()" from writing
1439 # this "invalidate()" prevents "wlock.release()" from writing
1442 # changes of dirstate out after restoring from backup file
1440 # changes of dirstate out after restoring from backup file
1443 self.invalidate()
1441 self.invalidate()
1444 filename = self._actualfilename(tr)
1442 filename = self._actualfilename(tr)
1445 o = self._opener
1443 o = self._opener
1446 if util.samefile(o.join(backupname), o.join(filename)):
1444 if util.samefile(o.join(backupname), o.join(filename)):
1447 o.unlink(backupname)
1445 o.unlink(backupname)
1448 else:
1446 else:
1449 o.rename(backupname, filename, checkambig=True)
1447 o.rename(backupname, filename, checkambig=True)
1450
1448
1451 def clearbackup(self, tr, backupname):
1449 def clearbackup(self, tr, backupname):
1452 '''Clear backup file'''
1450 '''Clear backup file'''
1453 self._opener.unlink(backupname)
1451 self._opener.unlink(backupname)
@@ -1,514 +1,519
1 # parsers.py - Python implementation of parsers.c
1 # parsers.py - Python implementation of parsers.c
2 #
2 #
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11 import zlib
11 import zlib
12
12
13 from ..node import (
13 from ..node import (
14 nullrev,
14 nullrev,
15 sha1nodeconstants,
15 sha1nodeconstants,
16 )
16 )
17 from .. import (
17 from .. import (
18 error,
18 error,
19 pycompat,
19 pycompat,
20 revlogutils,
20 revlogutils,
21 util,
21 util,
22 )
22 )
23
23
24 from ..revlogutils import nodemap as nodemaputil
24 from ..revlogutils import nodemap as nodemaputil
25 from ..revlogutils import constants as revlog_constants
25 from ..revlogutils import constants as revlog_constants
26
26
27 stringio = pycompat.bytesio
27 stringio = pycompat.bytesio
28
28
29
29
30 _pack = struct.pack
30 _pack = struct.pack
31 _unpack = struct.unpack
31 _unpack = struct.unpack
32 _compress = zlib.compress
32 _compress = zlib.compress
33 _decompress = zlib.decompress
33 _decompress = zlib.decompress
34
34
35
35
36 # a special value used internally for `size` if the file come from the other parent
36 # a special value used internally for `size` if the file come from the other parent
37 FROM_P2 = -2
37 FROM_P2 = -2
38
38
39
39
40 class dirstatetuple(object):
40 class dirstatetuple(object):
41 """represent a dirstate entry
41 """represent a dirstate entry
42
42
43 It contains:
43 It contains:
44
44
45 - state (one of 'n', 'a', 'r', 'm')
45 - state (one of 'n', 'a', 'r', 'm')
46 - mode,
46 - mode,
47 - size,
47 - size,
48 - mtime,
48 - mtime,
49 """
49 """
50
50
51 __slot__ = ('_state', '_mode', '_size', '_mtime')
51 __slot__ = ('_state', '_mode', '_size', '_mtime')
52
52
53 def __init__(self, state, mode, size, mtime):
53 def __init__(self, state, mode, size, mtime):
54 self._state = state
54 self._state = state
55 self._mode = mode
55 self._mode = mode
56 self._size = size
56 self._size = size
57 self._mtime = mtime
57 self._mtime = mtime
58
58
59 def __getitem__(self, idx):
59 def __getitem__(self, idx):
60 if idx == 0 or idx == -4:
60 if idx == 0 or idx == -4:
61 return self._state
61 return self._state
62 elif idx == 1 or idx == -3:
62 elif idx == 1 or idx == -3:
63 return self._mode
63 return self._mode
64 elif idx == 2 or idx == -2:
64 elif idx == 2 or idx == -2:
65 return self._size
65 return self._size
66 elif idx == 3 or idx == -1:
66 elif idx == 3 or idx == -1:
67 return self._mtime
67 return self._mtime
68 else:
68 else:
69 raise IndexError(idx)
69 raise IndexError(idx)
70
70
71 @property
71 @property
72 def state(self):
72 def state(self):
73 """
73 """
74 States are:
74 States are:
75 n normal
75 n normal
76 m needs merging
76 m needs merging
77 r marked for removal
77 r marked for removal
78 a marked for addition
78 a marked for addition
79
79
80 XXX This "state" is a bit obscure and mostly a direct expression of the
80 XXX This "state" is a bit obscure and mostly a direct expression of the
81 dirstatev1 format. It would make sense to ultimately deprecate it in
81 dirstatev1 format. It would make sense to ultimately deprecate it in
82 favor of the more "semantic" attributes.
82 favor of the more "semantic" attributes.
83 """
83 """
84 return self._state
84 return self._state
85
85
86 @property
86 @property
87 def merged(self):
87 def merged(self):
88 """True if the file has been merged
88 """True if the file has been merged
89
89
90 Should only be set if a merge is in progress in the dirstate
90 Should only be set if a merge is in progress in the dirstate
91 """
91 """
92 return self._state == b'm'
92 return self._state == b'm'
93
93
94 @property
94 @property
95 def from_p2(self):
95 def from_p2(self):
96 """True if the file have been fetched from p2 during the current merge
96 """True if the file have been fetched from p2 during the current merge
97
97
98 Should only be set if a merge is in progress in the dirstate
98 Should only be set if a merge is in progress in the dirstate
99 """
99 """
100 return self._size == FROM_P2
100 return self._size == FROM_P2
101
101
102 @property
103 def removed(self):
104 """True if the file has been removed"""
105 return self._state == b'r'
106
102 def v1_state(self):
107 def v1_state(self):
103 """return a "state" suitable for v1 serialization"""
108 """return a "state" suitable for v1 serialization"""
104 return self._state
109 return self._state
105
110
106 def v1_mode(self):
111 def v1_mode(self):
107 """return a "mode" suitable for v1 serialization"""
112 """return a "mode" suitable for v1 serialization"""
108 return self._mode
113 return self._mode
109
114
110 def v1_size(self):
115 def v1_size(self):
111 """return a "size" suitable for v1 serialization"""
116 """return a "size" suitable for v1 serialization"""
112 return self._size
117 return self._size
113
118
114 def v1_mtime(self):
119 def v1_mtime(self):
115 """return a "mtime" suitable for v1 serialization"""
120 """return a "mtime" suitable for v1 serialization"""
116 return self._mtime
121 return self._mtime
117
122
118
123
119 def gettype(q):
124 def gettype(q):
120 return int(q & 0xFFFF)
125 return int(q & 0xFFFF)
121
126
122
127
123 class BaseIndexObject(object):
128 class BaseIndexObject(object):
124 # Can I be passed to an algorithme implemented in Rust ?
129 # Can I be passed to an algorithme implemented in Rust ?
125 rust_ext_compat = 0
130 rust_ext_compat = 0
126 # Format of an index entry according to Python's `struct` language
131 # Format of an index entry according to Python's `struct` language
127 index_format = revlog_constants.INDEX_ENTRY_V1
132 index_format = revlog_constants.INDEX_ENTRY_V1
128 # Size of a C unsigned long long int, platform independent
133 # Size of a C unsigned long long int, platform independent
129 big_int_size = struct.calcsize(b'>Q')
134 big_int_size = struct.calcsize(b'>Q')
130 # Size of a C long int, platform independent
135 # Size of a C long int, platform independent
131 int_size = struct.calcsize(b'>i')
136 int_size = struct.calcsize(b'>i')
132 # An empty index entry, used as a default value to be overridden, or nullrev
137 # An empty index entry, used as a default value to be overridden, or nullrev
133 null_item = (
138 null_item = (
134 0,
139 0,
135 0,
140 0,
136 0,
141 0,
137 -1,
142 -1,
138 -1,
143 -1,
139 -1,
144 -1,
140 -1,
145 -1,
141 sha1nodeconstants.nullid,
146 sha1nodeconstants.nullid,
142 0,
147 0,
143 0,
148 0,
144 revlog_constants.COMP_MODE_INLINE,
149 revlog_constants.COMP_MODE_INLINE,
145 revlog_constants.COMP_MODE_INLINE,
150 revlog_constants.COMP_MODE_INLINE,
146 )
151 )
147
152
148 @util.propertycache
153 @util.propertycache
149 def entry_size(self):
154 def entry_size(self):
150 return self.index_format.size
155 return self.index_format.size
151
156
152 @property
157 @property
153 def nodemap(self):
158 def nodemap(self):
154 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
159 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
155 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
160 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
156 return self._nodemap
161 return self._nodemap
157
162
158 @util.propertycache
163 @util.propertycache
159 def _nodemap(self):
164 def _nodemap(self):
160 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
165 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
161 for r in range(0, len(self)):
166 for r in range(0, len(self)):
162 n = self[r][7]
167 n = self[r][7]
163 nodemap[n] = r
168 nodemap[n] = r
164 return nodemap
169 return nodemap
165
170
166 def has_node(self, node):
171 def has_node(self, node):
167 """return True if the node exist in the index"""
172 """return True if the node exist in the index"""
168 return node in self._nodemap
173 return node in self._nodemap
169
174
170 def rev(self, node):
175 def rev(self, node):
171 """return a revision for a node
176 """return a revision for a node
172
177
173 If the node is unknown, raise a RevlogError"""
178 If the node is unknown, raise a RevlogError"""
174 return self._nodemap[node]
179 return self._nodemap[node]
175
180
176 def get_rev(self, node):
181 def get_rev(self, node):
177 """return a revision for a node
182 """return a revision for a node
178
183
179 If the node is unknown, return None"""
184 If the node is unknown, return None"""
180 return self._nodemap.get(node)
185 return self._nodemap.get(node)
181
186
182 def _stripnodes(self, start):
187 def _stripnodes(self, start):
183 if '_nodemap' in vars(self):
188 if '_nodemap' in vars(self):
184 for r in range(start, len(self)):
189 for r in range(start, len(self)):
185 n = self[r][7]
190 n = self[r][7]
186 del self._nodemap[n]
191 del self._nodemap[n]
187
192
188 def clearcaches(self):
193 def clearcaches(self):
189 self.__dict__.pop('_nodemap', None)
194 self.__dict__.pop('_nodemap', None)
190
195
191 def __len__(self):
196 def __len__(self):
192 return self._lgt + len(self._extra)
197 return self._lgt + len(self._extra)
193
198
194 def append(self, tup):
199 def append(self, tup):
195 if '_nodemap' in vars(self):
200 if '_nodemap' in vars(self):
196 self._nodemap[tup[7]] = len(self)
201 self._nodemap[tup[7]] = len(self)
197 data = self._pack_entry(len(self), tup)
202 data = self._pack_entry(len(self), tup)
198 self._extra.append(data)
203 self._extra.append(data)
199
204
200 def _pack_entry(self, rev, entry):
205 def _pack_entry(self, rev, entry):
201 assert entry[8] == 0
206 assert entry[8] == 0
202 assert entry[9] == 0
207 assert entry[9] == 0
203 return self.index_format.pack(*entry[:8])
208 return self.index_format.pack(*entry[:8])
204
209
205 def _check_index(self, i):
210 def _check_index(self, i):
206 if not isinstance(i, int):
211 if not isinstance(i, int):
207 raise TypeError(b"expecting int indexes")
212 raise TypeError(b"expecting int indexes")
208 if i < 0 or i >= len(self):
213 if i < 0 or i >= len(self):
209 raise IndexError
214 raise IndexError
210
215
211 def __getitem__(self, i):
216 def __getitem__(self, i):
212 if i == -1:
217 if i == -1:
213 return self.null_item
218 return self.null_item
214 self._check_index(i)
219 self._check_index(i)
215 if i >= self._lgt:
220 if i >= self._lgt:
216 data = self._extra[i - self._lgt]
221 data = self._extra[i - self._lgt]
217 else:
222 else:
218 index = self._calculate_index(i)
223 index = self._calculate_index(i)
219 data = self._data[index : index + self.entry_size]
224 data = self._data[index : index + self.entry_size]
220 r = self._unpack_entry(i, data)
225 r = self._unpack_entry(i, data)
221 if self._lgt and i == 0:
226 if self._lgt and i == 0:
222 offset = revlogutils.offset_type(0, gettype(r[0]))
227 offset = revlogutils.offset_type(0, gettype(r[0]))
223 r = (offset,) + r[1:]
228 r = (offset,) + r[1:]
224 return r
229 return r
225
230
226 def _unpack_entry(self, rev, data):
231 def _unpack_entry(self, rev, data):
227 r = self.index_format.unpack(data)
232 r = self.index_format.unpack(data)
228 r = r + (
233 r = r + (
229 0,
234 0,
230 0,
235 0,
231 revlog_constants.COMP_MODE_INLINE,
236 revlog_constants.COMP_MODE_INLINE,
232 revlog_constants.COMP_MODE_INLINE,
237 revlog_constants.COMP_MODE_INLINE,
233 )
238 )
234 return r
239 return r
235
240
236 def pack_header(self, header):
241 def pack_header(self, header):
237 """pack header information as binary"""
242 """pack header information as binary"""
238 v_fmt = revlog_constants.INDEX_HEADER
243 v_fmt = revlog_constants.INDEX_HEADER
239 return v_fmt.pack(header)
244 return v_fmt.pack(header)
240
245
241 def entry_binary(self, rev):
246 def entry_binary(self, rev):
242 """return the raw binary string representing a revision"""
247 """return the raw binary string representing a revision"""
243 entry = self[rev]
248 entry = self[rev]
244 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
249 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
245 if rev == 0:
250 if rev == 0:
246 p = p[revlog_constants.INDEX_HEADER.size :]
251 p = p[revlog_constants.INDEX_HEADER.size :]
247 return p
252 return p
248
253
249
254
250 class IndexObject(BaseIndexObject):
255 class IndexObject(BaseIndexObject):
251 def __init__(self, data):
256 def __init__(self, data):
252 assert len(data) % self.entry_size == 0, (
257 assert len(data) % self.entry_size == 0, (
253 len(data),
258 len(data),
254 self.entry_size,
259 self.entry_size,
255 len(data) % self.entry_size,
260 len(data) % self.entry_size,
256 )
261 )
257 self._data = data
262 self._data = data
258 self._lgt = len(data) // self.entry_size
263 self._lgt = len(data) // self.entry_size
259 self._extra = []
264 self._extra = []
260
265
261 def _calculate_index(self, i):
266 def _calculate_index(self, i):
262 return i * self.entry_size
267 return i * self.entry_size
263
268
264 def __delitem__(self, i):
269 def __delitem__(self, i):
265 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
270 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
266 raise ValueError(b"deleting slices only supports a:-1 with step 1")
271 raise ValueError(b"deleting slices only supports a:-1 with step 1")
267 i = i.start
272 i = i.start
268 self._check_index(i)
273 self._check_index(i)
269 self._stripnodes(i)
274 self._stripnodes(i)
270 if i < self._lgt:
275 if i < self._lgt:
271 self._data = self._data[: i * self.entry_size]
276 self._data = self._data[: i * self.entry_size]
272 self._lgt = i
277 self._lgt = i
273 self._extra = []
278 self._extra = []
274 else:
279 else:
275 self._extra = self._extra[: i - self._lgt]
280 self._extra = self._extra[: i - self._lgt]
276
281
277
282
278 class PersistentNodeMapIndexObject(IndexObject):
283 class PersistentNodeMapIndexObject(IndexObject):
279 """a Debug oriented class to test persistent nodemap
284 """a Debug oriented class to test persistent nodemap
280
285
281 We need a simple python object to test API and higher level behavior. See
286 We need a simple python object to test API and higher level behavior. See
282 the Rust implementation for more serious usage. This should be used only
287 the Rust implementation for more serious usage. This should be used only
283 through the dedicated `devel.persistent-nodemap` config.
288 through the dedicated `devel.persistent-nodemap` config.
284 """
289 """
285
290
286 def nodemap_data_all(self):
291 def nodemap_data_all(self):
287 """Return bytes containing a full serialization of a nodemap
292 """Return bytes containing a full serialization of a nodemap
288
293
289 The nodemap should be valid for the full set of revisions in the
294 The nodemap should be valid for the full set of revisions in the
290 index."""
295 index."""
291 return nodemaputil.persistent_data(self)
296 return nodemaputil.persistent_data(self)
292
297
293 def nodemap_data_incremental(self):
298 def nodemap_data_incremental(self):
294 """Return bytes containing a incremental update to persistent nodemap
299 """Return bytes containing a incremental update to persistent nodemap
295
300
296 This containst the data for an append-only update of the data provided
301 This containst the data for an append-only update of the data provided
297 in the last call to `update_nodemap_data`.
302 in the last call to `update_nodemap_data`.
298 """
303 """
299 if self._nm_root is None:
304 if self._nm_root is None:
300 return None
305 return None
301 docket = self._nm_docket
306 docket = self._nm_docket
302 changed, data = nodemaputil.update_persistent_data(
307 changed, data = nodemaputil.update_persistent_data(
303 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
308 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
304 )
309 )
305
310
306 self._nm_root = self._nm_max_idx = self._nm_docket = None
311 self._nm_root = self._nm_max_idx = self._nm_docket = None
307 return docket, changed, data
312 return docket, changed, data
308
313
309 def update_nodemap_data(self, docket, nm_data):
314 def update_nodemap_data(self, docket, nm_data):
310 """provide full block of persisted binary data for a nodemap
315 """provide full block of persisted binary data for a nodemap
311
316
312 The data are expected to come from disk. See `nodemap_data_all` for a
317 The data are expected to come from disk. See `nodemap_data_all` for a
313 produceur of such data."""
318 produceur of such data."""
314 if nm_data is not None:
319 if nm_data is not None:
315 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
320 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
316 if self._nm_root:
321 if self._nm_root:
317 self._nm_docket = docket
322 self._nm_docket = docket
318 else:
323 else:
319 self._nm_root = self._nm_max_idx = self._nm_docket = None
324 self._nm_root = self._nm_max_idx = self._nm_docket = None
320
325
321
326
322 class InlinedIndexObject(BaseIndexObject):
327 class InlinedIndexObject(BaseIndexObject):
323 def __init__(self, data, inline=0):
328 def __init__(self, data, inline=0):
324 self._data = data
329 self._data = data
325 self._lgt = self._inline_scan(None)
330 self._lgt = self._inline_scan(None)
326 self._inline_scan(self._lgt)
331 self._inline_scan(self._lgt)
327 self._extra = []
332 self._extra = []
328
333
329 def _inline_scan(self, lgt):
334 def _inline_scan(self, lgt):
330 off = 0
335 off = 0
331 if lgt is not None:
336 if lgt is not None:
332 self._offsets = [0] * lgt
337 self._offsets = [0] * lgt
333 count = 0
338 count = 0
334 while off <= len(self._data) - self.entry_size:
339 while off <= len(self._data) - self.entry_size:
335 start = off + self.big_int_size
340 start = off + self.big_int_size
336 (s,) = struct.unpack(
341 (s,) = struct.unpack(
337 b'>i',
342 b'>i',
338 self._data[start : start + self.int_size],
343 self._data[start : start + self.int_size],
339 )
344 )
340 if lgt is not None:
345 if lgt is not None:
341 self._offsets[count] = off
346 self._offsets[count] = off
342 count += 1
347 count += 1
343 off += self.entry_size + s
348 off += self.entry_size + s
344 if off != len(self._data):
349 if off != len(self._data):
345 raise ValueError(b"corrupted data")
350 raise ValueError(b"corrupted data")
346 return count
351 return count
347
352
348 def __delitem__(self, i):
353 def __delitem__(self, i):
349 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
354 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
350 raise ValueError(b"deleting slices only supports a:-1 with step 1")
355 raise ValueError(b"deleting slices only supports a:-1 with step 1")
351 i = i.start
356 i = i.start
352 self._check_index(i)
357 self._check_index(i)
353 self._stripnodes(i)
358 self._stripnodes(i)
354 if i < self._lgt:
359 if i < self._lgt:
355 self._offsets = self._offsets[:i]
360 self._offsets = self._offsets[:i]
356 self._lgt = i
361 self._lgt = i
357 self._extra = []
362 self._extra = []
358 else:
363 else:
359 self._extra = self._extra[: i - self._lgt]
364 self._extra = self._extra[: i - self._lgt]
360
365
361 def _calculate_index(self, i):
366 def _calculate_index(self, i):
362 return self._offsets[i]
367 return self._offsets[i]
363
368
364
369
365 def parse_index2(data, inline, revlogv2=False):
370 def parse_index2(data, inline, revlogv2=False):
366 if not inline:
371 if not inline:
367 cls = IndexObject2 if revlogv2 else IndexObject
372 cls = IndexObject2 if revlogv2 else IndexObject
368 return cls(data), None
373 return cls(data), None
369 cls = InlinedIndexObject
374 cls = InlinedIndexObject
370 return cls(data, inline), (0, data)
375 return cls(data, inline), (0, data)
371
376
372
377
373 def parse_index_cl_v2(data):
378 def parse_index_cl_v2(data):
374 return IndexChangelogV2(data), None
379 return IndexChangelogV2(data), None
375
380
376
381
377 class IndexObject2(IndexObject):
382 class IndexObject2(IndexObject):
378 index_format = revlog_constants.INDEX_ENTRY_V2
383 index_format = revlog_constants.INDEX_ENTRY_V2
379
384
380 def replace_sidedata_info(
385 def replace_sidedata_info(
381 self,
386 self,
382 rev,
387 rev,
383 sidedata_offset,
388 sidedata_offset,
384 sidedata_length,
389 sidedata_length,
385 offset_flags,
390 offset_flags,
386 compression_mode,
391 compression_mode,
387 ):
392 ):
388 """
393 """
389 Replace an existing index entry's sidedata offset and length with new
394 Replace an existing index entry's sidedata offset and length with new
390 ones.
395 ones.
391 This cannot be used outside of the context of sidedata rewriting,
396 This cannot be used outside of the context of sidedata rewriting,
392 inside the transaction that creates the revision `rev`.
397 inside the transaction that creates the revision `rev`.
393 """
398 """
394 if rev < 0:
399 if rev < 0:
395 raise KeyError
400 raise KeyError
396 self._check_index(rev)
401 self._check_index(rev)
397 if rev < self._lgt:
402 if rev < self._lgt:
398 msg = b"cannot rewrite entries outside of this transaction"
403 msg = b"cannot rewrite entries outside of this transaction"
399 raise KeyError(msg)
404 raise KeyError(msg)
400 else:
405 else:
401 entry = list(self[rev])
406 entry = list(self[rev])
402 entry[0] = offset_flags
407 entry[0] = offset_flags
403 entry[8] = sidedata_offset
408 entry[8] = sidedata_offset
404 entry[9] = sidedata_length
409 entry[9] = sidedata_length
405 entry[11] = compression_mode
410 entry[11] = compression_mode
406 entry = tuple(entry)
411 entry = tuple(entry)
407 new = self._pack_entry(rev, entry)
412 new = self._pack_entry(rev, entry)
408 self._extra[rev - self._lgt] = new
413 self._extra[rev - self._lgt] = new
409
414
410 def _unpack_entry(self, rev, data):
415 def _unpack_entry(self, rev, data):
411 data = self.index_format.unpack(data)
416 data = self.index_format.unpack(data)
412 entry = data[:10]
417 entry = data[:10]
413 data_comp = data[10] & 3
418 data_comp = data[10] & 3
414 sidedata_comp = (data[10] & (3 << 2)) >> 2
419 sidedata_comp = (data[10] & (3 << 2)) >> 2
415 return entry + (data_comp, sidedata_comp)
420 return entry + (data_comp, sidedata_comp)
416
421
417 def _pack_entry(self, rev, entry):
422 def _pack_entry(self, rev, entry):
418 data = entry[:10]
423 data = entry[:10]
419 data_comp = entry[10] & 3
424 data_comp = entry[10] & 3
420 sidedata_comp = (entry[11] & 3) << 2
425 sidedata_comp = (entry[11] & 3) << 2
421 data += (data_comp | sidedata_comp,)
426 data += (data_comp | sidedata_comp,)
422
427
423 return self.index_format.pack(*data)
428 return self.index_format.pack(*data)
424
429
425 def entry_binary(self, rev):
430 def entry_binary(self, rev):
426 """return the raw binary string representing a revision"""
431 """return the raw binary string representing a revision"""
427 entry = self[rev]
432 entry = self[rev]
428 return self._pack_entry(rev, entry)
433 return self._pack_entry(rev, entry)
429
434
430 def pack_header(self, header):
435 def pack_header(self, header):
431 """pack header information as binary"""
436 """pack header information as binary"""
432 msg = 'version header should go in the docket, not the index: %d'
437 msg = 'version header should go in the docket, not the index: %d'
433 msg %= header
438 msg %= header
434 raise error.ProgrammingError(msg)
439 raise error.ProgrammingError(msg)
435
440
436
441
437 class IndexChangelogV2(IndexObject2):
442 class IndexChangelogV2(IndexObject2):
438 index_format = revlog_constants.INDEX_ENTRY_CL_V2
443 index_format = revlog_constants.INDEX_ENTRY_CL_V2
439
444
440 def _unpack_entry(self, rev, data, r=True):
445 def _unpack_entry(self, rev, data, r=True):
441 items = self.index_format.unpack(data)
446 items = self.index_format.unpack(data)
442 entry = items[:3] + (rev, rev) + items[3:8]
447 entry = items[:3] + (rev, rev) + items[3:8]
443 data_comp = items[8] & 3
448 data_comp = items[8] & 3
444 sidedata_comp = (items[8] >> 2) & 3
449 sidedata_comp = (items[8] >> 2) & 3
445 return entry + (data_comp, sidedata_comp)
450 return entry + (data_comp, sidedata_comp)
446
451
447 def _pack_entry(self, rev, entry):
452 def _pack_entry(self, rev, entry):
448 assert entry[3] == rev, entry[3]
453 assert entry[3] == rev, entry[3]
449 assert entry[4] == rev, entry[4]
454 assert entry[4] == rev, entry[4]
450 data = entry[:3] + entry[5:10]
455 data = entry[:3] + entry[5:10]
451 data_comp = entry[10] & 3
456 data_comp = entry[10] & 3
452 sidedata_comp = (entry[11] & 3) << 2
457 sidedata_comp = (entry[11] & 3) << 2
453 data += (data_comp | sidedata_comp,)
458 data += (data_comp | sidedata_comp,)
454 return self.index_format.pack(*data)
459 return self.index_format.pack(*data)
455
460
456
461
457 def parse_index_devel_nodemap(data, inline):
462 def parse_index_devel_nodemap(data, inline):
458 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
463 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
459 return PersistentNodeMapIndexObject(data), None
464 return PersistentNodeMapIndexObject(data), None
460
465
461
466
462 def parse_dirstate(dmap, copymap, st):
467 def parse_dirstate(dmap, copymap, st):
463 parents = [st[:20], st[20:40]]
468 parents = [st[:20], st[20:40]]
464 # dereference fields so they will be local in loop
469 # dereference fields so they will be local in loop
465 format = b">cllll"
470 format = b">cllll"
466 e_size = struct.calcsize(format)
471 e_size = struct.calcsize(format)
467 pos1 = 40
472 pos1 = 40
468 l = len(st)
473 l = len(st)
469
474
470 # the inner loop
475 # the inner loop
471 while pos1 < l:
476 while pos1 < l:
472 pos2 = pos1 + e_size
477 pos2 = pos1 + e_size
473 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
478 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
474 pos1 = pos2 + e[4]
479 pos1 = pos2 + e[4]
475 f = st[pos2:pos1]
480 f = st[pos2:pos1]
476 if b'\0' in f:
481 if b'\0' in f:
477 f, c = f.split(b'\0')
482 f, c = f.split(b'\0')
478 copymap[f] = c
483 copymap[f] = c
479 dmap[f] = dirstatetuple(*e[:4])
484 dmap[f] = dirstatetuple(*e[:4])
480 return parents
485 return parents
481
486
482
487
483 def pack_dirstate(dmap, copymap, pl, now):
488 def pack_dirstate(dmap, copymap, pl, now):
484 now = int(now)
489 now = int(now)
485 cs = stringio()
490 cs = stringio()
486 write = cs.write
491 write = cs.write
487 write(b"".join(pl))
492 write(b"".join(pl))
488 for f, e in pycompat.iteritems(dmap):
493 for f, e in pycompat.iteritems(dmap):
489 if e[0] == b'n' and e[3] == now:
494 if e[0] == b'n' and e[3] == now:
490 # The file was last modified "simultaneously" with the current
495 # The file was last modified "simultaneously" with the current
491 # write to dirstate (i.e. within the same second for file-
496 # write to dirstate (i.e. within the same second for file-
492 # systems with a granularity of 1 sec). This commonly happens
497 # systems with a granularity of 1 sec). This commonly happens
493 # for at least a couple of files on 'update'.
498 # for at least a couple of files on 'update'.
494 # The user could change the file without changing its size
499 # The user could change the file without changing its size
495 # within the same second. Invalidate the file's mtime in
500 # within the same second. Invalidate the file's mtime in
496 # dirstate, forcing future 'status' calls to compare the
501 # dirstate, forcing future 'status' calls to compare the
497 # contents of the file if the size is the same. This prevents
502 # contents of the file if the size is the same. This prevents
498 # mistakenly treating such files as clean.
503 # mistakenly treating such files as clean.
499 e = dirstatetuple(e[0], e[1], e[2], -1)
504 e = dirstatetuple(e[0], e[1], e[2], -1)
500 dmap[f] = e
505 dmap[f] = e
501
506
502 if f in copymap:
507 if f in copymap:
503 f = b"%s\0%s" % (f, copymap[f])
508 f = b"%s\0%s" % (f, copymap[f])
504 e = _pack(
509 e = _pack(
505 b">cllll",
510 b">cllll",
506 e.v1_state(),
511 e.v1_state(),
507 e.v1_mode(),
512 e.v1_mode(),
508 e.v1_size(),
513 e.v1_size(),
509 e.v1_mtime(),
514 e.v1_mtime(),
510 len(f),
515 len(f),
511 )
516 )
512 write(e)
517 write(e)
513 write(f)
518 write(f)
514 return cs.getvalue()
519 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now