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