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