##// END OF EJS Templates
dirstate-item: add dedicated "legacy" constructor for `addfile` case...
marmoute -
r48716:c0d6a59a default
parent child Browse files
Show More
@@ -1,1083 +1,1194 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 inline dirstateItemObject *make_dirstate_item(char state, int mode,
47 static inline dirstateItemObject *make_dirstate_item(char state, int mode,
48 int size, int mtime)
48 int size, int mtime)
49 {
49 {
50 dirstateItemObject *t =
50 dirstateItemObject *t =
51 PyObject_New(dirstateItemObject, &dirstateItemType);
51 PyObject_New(dirstateItemObject, &dirstateItemType);
52 if (!t) {
52 if (!t) {
53 return NULL;
53 return NULL;
54 }
54 }
55 t->state = state;
55 t->state = state;
56 t->mode = mode;
56 t->mode = mode;
57 t->size = size;
57 t->size = size;
58 t->mtime = mtime;
58 t->mtime = mtime;
59 return t;
59 return t;
60 }
60 }
61
61
62 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
62 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
63 PyObject *kwds)
63 PyObject *kwds)
64 {
64 {
65 /* We do all the initialization here and not a tp_init function because
65 /* We do all the initialization here and not a tp_init function because
66 * dirstate_item is immutable. */
66 * dirstate_item is immutable. */
67 dirstateItemObject *t;
67 dirstateItemObject *t;
68 int wc_tracked;
68 int wc_tracked;
69 int p1_tracked;
69 int p1_tracked;
70 int p2_tracked;
70 int p2_tracked;
71 int merged;
71 int merged;
72 int clean_p1;
72 int clean_p1;
73 int clean_p2;
73 int clean_p2;
74 int possibly_dirty;
74 int possibly_dirty;
75 PyObject *parentfiledata;
75 PyObject *parentfiledata;
76 static char *keywords_name[] = {
76 static char *keywords_name[] = {
77 "wc_tracked", "p1_tracked", "p2_tracked",
77 "wc_tracked", "p1_tracked", "p2_tracked",
78 "merged", "clean_p1", "clean_p2",
78 "merged", "clean_p1", "clean_p2",
79 "possibly_dirty", "parentfiledata", NULL,
79 "possibly_dirty", "parentfiledata", NULL,
80 };
80 };
81 wc_tracked = 0;
81 wc_tracked = 0;
82 p1_tracked = 0;
82 p1_tracked = 0;
83 p2_tracked = 0;
83 p2_tracked = 0;
84 merged = 0;
84 merged = 0;
85 clean_p1 = 0;
85 clean_p1 = 0;
86 clean_p2 = 0;
86 clean_p2 = 0;
87 possibly_dirty = 0;
87 possibly_dirty = 0;
88 parentfiledata = Py_None;
88 parentfiledata = Py_None;
89 if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiiiiiiO", keywords_name,
89 if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiiiiiiO", keywords_name,
90 &wc_tracked, &p1_tracked, &p2_tracked,
90 &wc_tracked, &p1_tracked, &p2_tracked,
91 &merged, &clean_p1, &clean_p2,
91 &merged, &clean_p1, &clean_p2,
92 &possibly_dirty, &parentfiledata
92 &possibly_dirty, &parentfiledata
93
93
94 )) {
94 )) {
95 return NULL;
95 return NULL;
96 }
96 }
97 if (merged && (clean_p1 || clean_p2)) {
97 if (merged && (clean_p1 || clean_p2)) {
98 PyErr_SetString(PyExc_RuntimeError,
98 PyErr_SetString(PyExc_RuntimeError,
99 "`merged` argument incompatible with "
99 "`merged` argument incompatible with "
100 "`clean_p1`/`clean_p2`");
100 "`clean_p1`/`clean_p2`");
101 return NULL;
101 return NULL;
102 }
102 }
103 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
103 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
104 if (!t) {
104 if (!t) {
105 return NULL;
105 return NULL;
106 }
106 }
107 t->state = 'r';
107 t->state = 'r';
108 t->mode = 0;
108 t->mode = 0;
109 t->size = dirstate_v1_nonnormal;
109 t->size = dirstate_v1_nonnormal;
110 t->mtime = ambiguous_time;
110 t->mtime = ambiguous_time;
111 if (!(p1_tracked || p2_tracked || wc_tracked)) {
111 if (!(p1_tracked || p2_tracked || wc_tracked)) {
112 /* Nothing special to do, file is untracked */
112 /* Nothing special to do, file is untracked */
113 } else if (merged) {
113 } else if (merged) {
114 t->state = 'm';
114 t->state = 'm';
115 t->size = dirstate_v1_from_p2;
115 t->size = dirstate_v1_from_p2;
116 t->mtime = ambiguous_time;
116 t->mtime = ambiguous_time;
117 } else if (!(p1_tracked || p2_tracked) && wc_tracked) {
117 } else if (!(p1_tracked || p2_tracked) && wc_tracked) {
118 t->state = 'a';
118 t->state = 'a';
119 t->size = dirstate_v1_nonnormal;
119 t->size = dirstate_v1_nonnormal;
120 t->mtime = ambiguous_time;
120 t->mtime = ambiguous_time;
121 } else if ((p1_tracked || p2_tracked) && !wc_tracked) {
121 } else if ((p1_tracked || p2_tracked) && !wc_tracked) {
122 t->state = 'r';
122 t->state = 'r';
123 t->size = 0;
123 t->size = 0;
124 t->mtime = 0;
124 t->mtime = 0;
125 } else if (clean_p2 && wc_tracked) {
125 } else if (clean_p2 && wc_tracked) {
126 t->state = 'n';
126 t->state = 'n';
127 t->size = dirstate_v1_from_p2;
127 t->size = dirstate_v1_from_p2;
128 t->mtime = ambiguous_time;
128 t->mtime = ambiguous_time;
129 } else if (!p1_tracked && p2_tracked && wc_tracked) {
129 } else if (!p1_tracked && p2_tracked && wc_tracked) {
130 t->state = 'n';
130 t->state = 'n';
131 t->size = dirstate_v1_from_p2;
131 t->size = dirstate_v1_from_p2;
132 t->mtime = ambiguous_time;
132 t->mtime = ambiguous_time;
133 } else if (possibly_dirty) {
133 } else if (possibly_dirty) {
134 t->state = 'n';
134 t->state = 'n';
135 t->size = dirstate_v1_nonnormal;
135 t->size = dirstate_v1_nonnormal;
136 t->mtime = ambiguous_time;
136 t->mtime = ambiguous_time;
137 } else if (wc_tracked) {
137 } else if (wc_tracked) {
138 /* this is a "normal" file */
138 /* this is a "normal" file */
139 if (parentfiledata == Py_None) {
139 if (parentfiledata == Py_None) {
140 PyErr_SetString(
140 PyErr_SetString(
141 PyExc_RuntimeError,
141 PyExc_RuntimeError,
142 "failed to pass parentfiledata for a normal file");
142 "failed to pass parentfiledata for a normal file");
143 return NULL;
143 return NULL;
144 }
144 }
145 if (!PyTuple_CheckExact(parentfiledata)) {
145 if (!PyTuple_CheckExact(parentfiledata)) {
146 PyErr_SetString(
146 PyErr_SetString(
147 PyExc_TypeError,
147 PyExc_TypeError,
148 "parentfiledata should be a Tuple or None");
148 "parentfiledata should be a Tuple or None");
149 return NULL;
149 return NULL;
150 }
150 }
151 t->state = 'n';
151 t->state = 'n';
152 t->mode =
152 t->mode =
153 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
153 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
154 t->size =
154 t->size =
155 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
155 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
156 t->mtime =
156 t->mtime =
157 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
157 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
158 } else {
158 } else {
159 PyErr_SetString(PyExc_RuntimeError, "unreachable");
159 PyErr_SetString(PyExc_RuntimeError, "unreachable");
160 return NULL;
160 return NULL;
161 }
161 }
162 return (PyObject *)t;
162 return (PyObject *)t;
163 }
163 }
164
164
165 static void dirstate_item_dealloc(PyObject *o)
165 static void dirstate_item_dealloc(PyObject *o)
166 {
166 {
167 PyObject_Del(o);
167 PyObject_Del(o);
168 }
168 }
169
169
170 static Py_ssize_t dirstate_item_length(PyObject *o)
170 static Py_ssize_t dirstate_item_length(PyObject *o)
171 {
171 {
172 return 4;
172 return 4;
173 }
173 }
174
174
175 static PyObject *dirstate_item_item(PyObject *o, Py_ssize_t i)
175 static PyObject *dirstate_item_item(PyObject *o, Py_ssize_t i)
176 {
176 {
177 dirstateItemObject *t = (dirstateItemObject *)o;
177 dirstateItemObject *t = (dirstateItemObject *)o;
178 switch (i) {
178 switch (i) {
179 case 0:
179 case 0:
180 return PyBytes_FromStringAndSize(&t->state, 1);
180 return PyBytes_FromStringAndSize(&t->state, 1);
181 case 1:
181 case 1:
182 return PyInt_FromLong(t->mode);
182 return PyInt_FromLong(t->mode);
183 case 2:
183 case 2:
184 return PyInt_FromLong(t->size);
184 return PyInt_FromLong(t->size);
185 case 3:
185 case 3:
186 return PyInt_FromLong(t->mtime);
186 return PyInt_FromLong(t->mtime);
187 default:
187 default:
188 PyErr_SetString(PyExc_IndexError, "index out of range");
188 PyErr_SetString(PyExc_IndexError, "index out of range");
189 return NULL;
189 return NULL;
190 }
190 }
191 }
191 }
192
192
193 static PySequenceMethods dirstate_item_sq = {
193 static PySequenceMethods dirstate_item_sq = {
194 dirstate_item_length, /* sq_length */
194 dirstate_item_length, /* sq_length */
195 0, /* sq_concat */
195 0, /* sq_concat */
196 0, /* sq_repeat */
196 0, /* sq_repeat */
197 dirstate_item_item, /* sq_item */
197 dirstate_item_item, /* sq_item */
198 0, /* sq_ass_item */
198 0, /* sq_ass_item */
199 0, /* sq_contains */
199 0, /* sq_contains */
200 0, /* sq_inplace_concat */
200 0, /* sq_inplace_concat */
201 0 /* sq_inplace_repeat */
201 0 /* sq_inplace_repeat */
202 };
202 };
203
203
204 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
204 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
205 {
205 {
206 return PyBytes_FromStringAndSize(&self->state, 1);
206 return PyBytes_FromStringAndSize(&self->state, 1);
207 };
207 };
208
208
209 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
209 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
210 {
210 {
211 return PyInt_FromLong(self->mode);
211 return PyInt_FromLong(self->mode);
212 };
212 };
213
213
214 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
214 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
215 {
215 {
216 return PyInt_FromLong(self->size);
216 return PyInt_FromLong(self->size);
217 };
217 };
218
218
219 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
219 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
220 {
220 {
221 return PyInt_FromLong(self->mtime);
221 return PyInt_FromLong(self->mtime);
222 };
222 };
223
223
224 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
224 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
225 PyObject *value)
225 PyObject *value)
226 {
226 {
227 long now;
227 long now;
228 if (!pylong_to_long(value, &now)) {
228 if (!pylong_to_long(value, &now)) {
229 return NULL;
229 return NULL;
230 }
230 }
231 if (self->state == 'n' && self->mtime == now) {
231 if (self->state == 'n' && self->mtime == now) {
232 Py_RETURN_TRUE;
232 Py_RETURN_TRUE;
233 } else {
233 } else {
234 Py_RETURN_FALSE;
234 Py_RETURN_FALSE;
235 }
235 }
236 };
236 };
237
237
238 /* This will never change since it's bound to V1, unlike `make_dirstate_item`
238 /* This will never change since it's bound to V1, unlike `make_dirstate_item`
239 */
239 */
240 static inline dirstateItemObject *
240 static inline dirstateItemObject *
241 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
241 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
242 {
242 {
243 dirstateItemObject *t =
243 dirstateItemObject *t =
244 PyObject_New(dirstateItemObject, &dirstateItemType);
244 PyObject_New(dirstateItemObject, &dirstateItemType);
245 if (!t) {
245 if (!t) {
246 return NULL;
246 return NULL;
247 }
247 }
248 t->state = state;
248 t->state = state;
249 t->mode = mode;
249 t->mode = mode;
250 t->size = size;
250 t->size = size;
251 t->mtime = mtime;
251 t->mtime = mtime;
252 return t;
252 return t;
253 }
253 }
254
254
255 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
255 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
256 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
256 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
257 PyObject *args)
257 PyObject *args)
258 {
258 {
259 /* We do all the initialization here and not a tp_init function because
259 /* We do all the initialization here and not a tp_init function because
260 * dirstate_item is immutable. */
260 * dirstate_item is immutable. */
261 dirstateItemObject *t;
261 dirstateItemObject *t;
262 char state;
262 char state;
263 int size, mode, mtime;
263 int size, mode, mtime;
264 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
264 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
265 return NULL;
265 return NULL;
266 }
266 }
267
267
268 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
268 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
269 if (!t) {
269 if (!t) {
270 return NULL;
270 return NULL;
271 }
271 }
272 t->state = state;
272 t->state = state;
273 t->mode = mode;
273 t->mode = mode;
274 t->size = size;
274 t->size = size;
275 t->mtime = mtime;
275 t->mtime = mtime;
276
276
277 return (PyObject *)t;
277 return (PyObject *)t;
278 };
278 };
279
279
280 /* constructor to help legacy API to build a new "added" item
281
282 Should eventually be removed */
283 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
284 {
285 dirstateItemObject *t;
286 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
287 if (!t) {
288 return NULL;
289 }
290 t->state = 'a';
291 t->mode = 0;
292 t->size = dirstate_v1_nonnormal;
293 t->mtime = ambiguous_time;
294 return (PyObject *)t;
295 };
296
297 /* constructor to help legacy API to build a new "merged" item
298
299 Should eventually be removed */
300 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
301 {
302 dirstateItemObject *t;
303 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
304 if (!t) {
305 return NULL;
306 }
307 t->state = 'm';
308 t->mode = 0;
309 t->size = dirstate_v1_from_p2;
310 t->mtime = ambiguous_time;
311 return (PyObject *)t;
312 };
313
314 /* constructor to help legacy API to build a new "from_p2" item
315
316 Should eventually be removed */
317 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
318 {
319 /* We do all the initialization here and not a tp_init function because
320 * dirstate_item is immutable. */
321 dirstateItemObject *t;
322 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
323 if (!t) {
324 return NULL;
325 }
326 t->state = 'n';
327 t->mode = 0;
328 t->size = dirstate_v1_from_p2;
329 t->mtime = ambiguous_time;
330 return (PyObject *)t;
331 };
332
333 /* constructor to help legacy API to build a new "possibly" item
334
335 Should eventually be removed */
336 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
337 {
338 /* We do all the initialization here and not a tp_init function because
339 * dirstate_item is immutable. */
340 dirstateItemObject *t;
341 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
342 if (!t) {
343 return NULL;
344 }
345 t->state = 'n';
346 t->mode = 0;
347 t->size = dirstate_v1_nonnormal;
348 t->mtime = ambiguous_time;
349 return (PyObject *)t;
350 };
351
352 /* constructor to help legacy API to build a new "normal" item
353
354 Should eventually be removed */
355 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
356 {
357 /* We do all the initialization here and not a tp_init function because
358 * dirstate_item is immutable. */
359 dirstateItemObject *t;
360 int size, mode, mtime;
361 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
362 return NULL;
363 }
364
365 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
366 if (!t) {
367 return NULL;
368 }
369 t->state = 'n';
370 t->mode = mode;
371 t->size = size;
372 t->mtime = mtime;
373 return (PyObject *)t;
374 };
375
280 /* This means the next status call will have to actually check its content
376 /* This means the next status call will have to actually check its content
281 to make sure it is correct. */
377 to make sure it is correct. */
282 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
378 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
283 {
379 {
284 self->mtime = ambiguous_time;
380 self->mtime = ambiguous_time;
285 Py_RETURN_NONE;
381 Py_RETURN_NONE;
286 }
382 }
287
383
288 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
384 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
289 {
385 {
290 if (self->state == 'm') {
386 if (self->state == 'm') {
291 self->size = dirstate_v1_nonnormal;
387 self->size = dirstate_v1_nonnormal;
292 } else if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
388 } else if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
293 self->size = dirstate_v1_from_p2;
389 self->size = dirstate_v1_from_p2;
294 } else {
390 } else {
295 self->size = 0;
391 self->size = 0;
296 }
392 }
297 self->state = 'r';
393 self->state = 'r';
298 self->mode = 0;
394 self->mode = 0;
299 self->mtime = 0;
395 self->mtime = 0;
300 Py_RETURN_NONE;
396 Py_RETURN_NONE;
301 }
397 }
302
398
303 static PyMethodDef dirstate_item_methods[] = {
399 static PyMethodDef dirstate_item_methods[] = {
304 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
400 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
305 "return a \"state\" suitable for v1 serialization"},
401 "return a \"state\" suitable for v1 serialization"},
306 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
402 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
307 "return a \"mode\" suitable for v1 serialization"},
403 "return a \"mode\" suitable for v1 serialization"},
308 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
404 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
309 "return a \"size\" suitable for v1 serialization"},
405 "return a \"size\" suitable for v1 serialization"},
310 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
406 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
311 "return a \"mtime\" suitable for v1 serialization"},
407 "return a \"mtime\" suitable for v1 serialization"},
312 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
408 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
313 "True if the stored mtime would be ambiguous with the current time"},
409 "True if the stored mtime would be ambiguous with the current time"},
314 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
410 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
315 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
411 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
412 {"new_added", (PyCFunction)dirstate_item_new_added,
413 METH_NOARGS | METH_CLASS,
414 "constructor to help legacy API to build a new \"added\" item"},
415 {"new_merged", (PyCFunction)dirstate_item_new_merged,
416 METH_NOARGS | METH_CLASS,
417 "constructor to help legacy API to build a new \"merged\" item"},
418 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
419 METH_NOARGS | METH_CLASS,
420 "constructor to help legacy API to build a new \"from_p2\" item"},
421 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
422 METH_NOARGS | METH_CLASS,
423 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
424 {"new_normal", (PyCFunction)dirstate_item_new_normal,
425 METH_VARARGS | METH_CLASS,
426 "constructor to help legacy API to build a new \"normal\" item"},
316 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
427 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
317 METH_NOARGS, "mark a file as \"possibly dirty\""},
428 METH_NOARGS, "mark a file as \"possibly dirty\""},
318 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
429 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
319 "mark a file as \"untracked\""},
430 "mark a file as \"untracked\""},
320 {NULL} /* Sentinel */
431 {NULL} /* Sentinel */
321 };
432 };
322
433
323 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
434 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
324 {
435 {
325 return PyInt_FromLong(self->mode);
436 return PyInt_FromLong(self->mode);
326 };
437 };
327
438
328 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
439 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
329 {
440 {
330 return PyInt_FromLong(self->size);
441 return PyInt_FromLong(self->size);
331 };
442 };
332
443
333 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
444 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
334 {
445 {
335 return PyInt_FromLong(self->mtime);
446 return PyInt_FromLong(self->mtime);
336 };
447 };
337
448
338 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
449 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
339 {
450 {
340 return PyBytes_FromStringAndSize(&self->state, 1);
451 return PyBytes_FromStringAndSize(&self->state, 1);
341 };
452 };
342
453
343 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
454 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
344 {
455 {
345 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
456 if (self->state == 'a' || self->state == 'm' || self->state == 'n') {
346 Py_RETURN_TRUE;
457 Py_RETURN_TRUE;
347 } else {
458 } else {
348 Py_RETURN_FALSE;
459 Py_RETURN_FALSE;
349 }
460 }
350 };
461 };
351
462
352 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
463 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
353 {
464 {
354 if (self->state == 'a') {
465 if (self->state == 'a') {
355 Py_RETURN_TRUE;
466 Py_RETURN_TRUE;
356 } else {
467 } else {
357 Py_RETURN_FALSE;
468 Py_RETURN_FALSE;
358 }
469 }
359 };
470 };
360
471
361 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
472 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
362 {
473 {
363 if (self->state == 'm') {
474 if (self->state == 'm') {
364 Py_RETURN_TRUE;
475 Py_RETURN_TRUE;
365 } else {
476 } else {
366 Py_RETURN_FALSE;
477 Py_RETURN_FALSE;
367 }
478 }
368 };
479 };
369
480
370 static PyObject *dirstate_item_get_merged_removed(dirstateItemObject *self)
481 static PyObject *dirstate_item_get_merged_removed(dirstateItemObject *self)
371 {
482 {
372 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
483 if (self->state == 'r' && self->size == dirstate_v1_nonnormal) {
373 Py_RETURN_TRUE;
484 Py_RETURN_TRUE;
374 } else {
485 } else {
375 Py_RETURN_FALSE;
486 Py_RETURN_FALSE;
376 }
487 }
377 };
488 };
378
489
379 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
490 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
380 {
491 {
381 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
492 if (self->state == 'n' && self->size == dirstate_v1_from_p2) {
382 Py_RETURN_TRUE;
493 Py_RETURN_TRUE;
383 } else {
494 } else {
384 Py_RETURN_FALSE;
495 Py_RETURN_FALSE;
385 }
496 }
386 };
497 };
387
498
388 static PyObject *dirstate_item_get_from_p2_removed(dirstateItemObject *self)
499 static PyObject *dirstate_item_get_from_p2_removed(dirstateItemObject *self)
389 {
500 {
390 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
501 if (self->state == 'r' && self->size == dirstate_v1_from_p2) {
391 Py_RETURN_TRUE;
502 Py_RETURN_TRUE;
392 } else {
503 } else {
393 Py_RETURN_FALSE;
504 Py_RETURN_FALSE;
394 }
505 }
395 };
506 };
396
507
397 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
508 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
398 {
509 {
399 if (self->state == 'r') {
510 if (self->state == 'r') {
400 Py_RETURN_TRUE;
511 Py_RETURN_TRUE;
401 } else {
512 } else {
402 Py_RETURN_FALSE;
513 Py_RETURN_FALSE;
403 }
514 }
404 };
515 };
405
516
406 static PyObject *dm_nonnormal(dirstateItemObject *self)
517 static PyObject *dm_nonnormal(dirstateItemObject *self)
407 {
518 {
408 if (self->state != 'n' || self->mtime == ambiguous_time) {
519 if (self->state != 'n' || self->mtime == ambiguous_time) {
409 Py_RETURN_TRUE;
520 Py_RETURN_TRUE;
410 } else {
521 } else {
411 Py_RETURN_FALSE;
522 Py_RETURN_FALSE;
412 }
523 }
413 };
524 };
414 static PyObject *dm_otherparent(dirstateItemObject *self)
525 static PyObject *dm_otherparent(dirstateItemObject *self)
415 {
526 {
416 if (self->size == dirstate_v1_from_p2) {
527 if (self->size == dirstate_v1_from_p2) {
417 Py_RETURN_TRUE;
528 Py_RETURN_TRUE;
418 } else {
529 } else {
419 Py_RETURN_FALSE;
530 Py_RETURN_FALSE;
420 }
531 }
421 };
532 };
422
533
423 static PyGetSetDef dirstate_item_getset[] = {
534 static PyGetSetDef dirstate_item_getset[] = {
424 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
535 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
425 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
536 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
426 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
537 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
427 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
538 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
428 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
539 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
429 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
540 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
430 {"merged_removed", (getter)dirstate_item_get_merged_removed, NULL,
541 {"merged_removed", (getter)dirstate_item_get_merged_removed, NULL,
431 "merged_removed", NULL},
542 "merged_removed", NULL},
432 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
543 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
433 {"from_p2_removed", (getter)dirstate_item_get_from_p2_removed, NULL,
544 {"from_p2_removed", (getter)dirstate_item_get_from_p2_removed, NULL,
434 "from_p2_removed", NULL},
545 "from_p2_removed", NULL},
435 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
546 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
436 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
547 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
437 {"dm_nonnormal", (getter)dm_nonnormal, NULL, "dm_nonnormal", NULL},
548 {"dm_nonnormal", (getter)dm_nonnormal, NULL, "dm_nonnormal", NULL},
438 {"dm_otherparent", (getter)dm_otherparent, NULL, "dm_otherparent", NULL},
549 {"dm_otherparent", (getter)dm_otherparent, NULL, "dm_otherparent", NULL},
439 {NULL} /* Sentinel */
550 {NULL} /* Sentinel */
440 };
551 };
441
552
442 PyTypeObject dirstateItemType = {
553 PyTypeObject dirstateItemType = {
443 PyVarObject_HEAD_INIT(NULL, 0) /* header */
554 PyVarObject_HEAD_INIT(NULL, 0) /* header */
444 "dirstate_tuple", /* tp_name */
555 "dirstate_tuple", /* tp_name */
445 sizeof(dirstateItemObject), /* tp_basicsize */
556 sizeof(dirstateItemObject), /* tp_basicsize */
446 0, /* tp_itemsize */
557 0, /* tp_itemsize */
447 (destructor)dirstate_item_dealloc, /* tp_dealloc */
558 (destructor)dirstate_item_dealloc, /* tp_dealloc */
448 0, /* tp_print */
559 0, /* tp_print */
449 0, /* tp_getattr */
560 0, /* tp_getattr */
450 0, /* tp_setattr */
561 0, /* tp_setattr */
451 0, /* tp_compare */
562 0, /* tp_compare */
452 0, /* tp_repr */
563 0, /* tp_repr */
453 0, /* tp_as_number */
564 0, /* tp_as_number */
454 &dirstate_item_sq, /* tp_as_sequence */
565 &dirstate_item_sq, /* tp_as_sequence */
455 0, /* tp_as_mapping */
566 0, /* tp_as_mapping */
456 0, /* tp_hash */
567 0, /* tp_hash */
457 0, /* tp_call */
568 0, /* tp_call */
458 0, /* tp_str */
569 0, /* tp_str */
459 0, /* tp_getattro */
570 0, /* tp_getattro */
460 0, /* tp_setattro */
571 0, /* tp_setattro */
461 0, /* tp_as_buffer */
572 0, /* tp_as_buffer */
462 Py_TPFLAGS_DEFAULT, /* tp_flags */
573 Py_TPFLAGS_DEFAULT, /* tp_flags */
463 "dirstate tuple", /* tp_doc */
574 "dirstate tuple", /* tp_doc */
464 0, /* tp_traverse */
575 0, /* tp_traverse */
465 0, /* tp_clear */
576 0, /* tp_clear */
466 0, /* tp_richcompare */
577 0, /* tp_richcompare */
467 0, /* tp_weaklistoffset */
578 0, /* tp_weaklistoffset */
468 0, /* tp_iter */
579 0, /* tp_iter */
469 0, /* tp_iternext */
580 0, /* tp_iternext */
470 dirstate_item_methods, /* tp_methods */
581 dirstate_item_methods, /* tp_methods */
471 0, /* tp_members */
582 0, /* tp_members */
472 dirstate_item_getset, /* tp_getset */
583 dirstate_item_getset, /* tp_getset */
473 0, /* tp_base */
584 0, /* tp_base */
474 0, /* tp_dict */
585 0, /* tp_dict */
475 0, /* tp_descr_get */
586 0, /* tp_descr_get */
476 0, /* tp_descr_set */
587 0, /* tp_descr_set */
477 0, /* tp_dictoffset */
588 0, /* tp_dictoffset */
478 0, /* tp_init */
589 0, /* tp_init */
479 0, /* tp_alloc */
590 0, /* tp_alloc */
480 dirstate_item_new, /* tp_new */
591 dirstate_item_new, /* tp_new */
481 };
592 };
482
593
483 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
594 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
484 {
595 {
485 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
596 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
486 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
597 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
487 char state, *cur, *str, *cpos;
598 char state, *cur, *str, *cpos;
488 int mode, size, mtime;
599 int mode, size, mtime;
489 unsigned int flen, pos = 40;
600 unsigned int flen, pos = 40;
490 Py_ssize_t len = 40;
601 Py_ssize_t len = 40;
491 Py_ssize_t readlen;
602 Py_ssize_t readlen;
492
603
493 if (!PyArg_ParseTuple(
604 if (!PyArg_ParseTuple(
494 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
605 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
495 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
606 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
496 goto quit;
607 goto quit;
497 }
608 }
498
609
499 len = readlen;
610 len = readlen;
500
611
501 /* read parents */
612 /* read parents */
502 if (len < 40) {
613 if (len < 40) {
503 PyErr_SetString(PyExc_ValueError,
614 PyErr_SetString(PyExc_ValueError,
504 "too little data for parents");
615 "too little data for parents");
505 goto quit;
616 goto quit;
506 }
617 }
507
618
508 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
619 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
509 str + 20, (Py_ssize_t)20);
620 str + 20, (Py_ssize_t)20);
510 if (!parents) {
621 if (!parents) {
511 goto quit;
622 goto quit;
512 }
623 }
513
624
514 /* read filenames */
625 /* read filenames */
515 while (pos >= 40 && pos < len) {
626 while (pos >= 40 && pos < len) {
516 if (pos + 17 > len) {
627 if (pos + 17 > len) {
517 PyErr_SetString(PyExc_ValueError,
628 PyErr_SetString(PyExc_ValueError,
518 "overflow in dirstate");
629 "overflow in dirstate");
519 goto quit;
630 goto quit;
520 }
631 }
521 cur = str + pos;
632 cur = str + pos;
522 /* unpack header */
633 /* unpack header */
523 state = *cur;
634 state = *cur;
524 mode = getbe32(cur + 1);
635 mode = getbe32(cur + 1);
525 size = getbe32(cur + 5);
636 size = getbe32(cur + 5);
526 mtime = getbe32(cur + 9);
637 mtime = getbe32(cur + 9);
527 flen = getbe32(cur + 13);
638 flen = getbe32(cur + 13);
528 pos += 17;
639 pos += 17;
529 cur += 17;
640 cur += 17;
530 if (flen > len - pos) {
641 if (flen > len - pos) {
531 PyErr_SetString(PyExc_ValueError,
642 PyErr_SetString(PyExc_ValueError,
532 "overflow in dirstate");
643 "overflow in dirstate");
533 goto quit;
644 goto quit;
534 }
645 }
535
646
536 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
647 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
537 size, mtime);
648 size, mtime);
538 cpos = memchr(cur, 0, flen);
649 cpos = memchr(cur, 0, flen);
539 if (cpos) {
650 if (cpos) {
540 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
651 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
541 cname = PyBytes_FromStringAndSize(
652 cname = PyBytes_FromStringAndSize(
542 cpos + 1, flen - (cpos - cur) - 1);
653 cpos + 1, flen - (cpos - cur) - 1);
543 if (!fname || !cname ||
654 if (!fname || !cname ||
544 PyDict_SetItem(cmap, fname, cname) == -1 ||
655 PyDict_SetItem(cmap, fname, cname) == -1 ||
545 PyDict_SetItem(dmap, fname, entry) == -1) {
656 PyDict_SetItem(dmap, fname, entry) == -1) {
546 goto quit;
657 goto quit;
547 }
658 }
548 Py_DECREF(cname);
659 Py_DECREF(cname);
549 } else {
660 } else {
550 fname = PyBytes_FromStringAndSize(cur, flen);
661 fname = PyBytes_FromStringAndSize(cur, flen);
551 if (!fname ||
662 if (!fname ||
552 PyDict_SetItem(dmap, fname, entry) == -1) {
663 PyDict_SetItem(dmap, fname, entry) == -1) {
553 goto quit;
664 goto quit;
554 }
665 }
555 }
666 }
556 Py_DECREF(fname);
667 Py_DECREF(fname);
557 Py_DECREF(entry);
668 Py_DECREF(entry);
558 fname = cname = entry = NULL;
669 fname = cname = entry = NULL;
559 pos += flen;
670 pos += flen;
560 }
671 }
561
672
562 ret = parents;
673 ret = parents;
563 Py_INCREF(ret);
674 Py_INCREF(ret);
564 quit:
675 quit:
565 Py_XDECREF(fname);
676 Py_XDECREF(fname);
566 Py_XDECREF(cname);
677 Py_XDECREF(cname);
567 Py_XDECREF(entry);
678 Py_XDECREF(entry);
568 Py_XDECREF(parents);
679 Py_XDECREF(parents);
569 return ret;
680 return ret;
570 }
681 }
571
682
572 /*
683 /*
573 * Build a set of non-normal and other parent entries from the dirstate dmap
684 * Build a set of non-normal and other parent entries from the dirstate dmap
574 */
685 */
575 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
686 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
576 {
687 {
577 PyObject *dmap, *fname, *v;
688 PyObject *dmap, *fname, *v;
578 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
689 PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
579 Py_ssize_t pos;
690 Py_ssize_t pos;
580
691
581 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
692 if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type,
582 &dmap)) {
693 &dmap)) {
583 goto bail;
694 goto bail;
584 }
695 }
585
696
586 nonnset = PySet_New(NULL);
697 nonnset = PySet_New(NULL);
587 if (nonnset == NULL) {
698 if (nonnset == NULL) {
588 goto bail;
699 goto bail;
589 }
700 }
590
701
591 otherpset = PySet_New(NULL);
702 otherpset = PySet_New(NULL);
592 if (otherpset == NULL) {
703 if (otherpset == NULL) {
593 goto bail;
704 goto bail;
594 }
705 }
595
706
596 pos = 0;
707 pos = 0;
597 while (PyDict_Next(dmap, &pos, &fname, &v)) {
708 while (PyDict_Next(dmap, &pos, &fname, &v)) {
598 dirstateItemObject *t;
709 dirstateItemObject *t;
599 if (!dirstate_tuple_check(v)) {
710 if (!dirstate_tuple_check(v)) {
600 PyErr_SetString(PyExc_TypeError,
711 PyErr_SetString(PyExc_TypeError,
601 "expected a dirstate tuple");
712 "expected a dirstate tuple");
602 goto bail;
713 goto bail;
603 }
714 }
604 t = (dirstateItemObject *)v;
715 t = (dirstateItemObject *)v;
605
716
606 if (t->state == 'n' && t->size == -2) {
717 if (t->state == 'n' && t->size == -2) {
607 if (PySet_Add(otherpset, fname) == -1) {
718 if (PySet_Add(otherpset, fname) == -1) {
608 goto bail;
719 goto bail;
609 }
720 }
610 }
721 }
611
722
612 if (t->state == 'n' && t->mtime != -1) {
723 if (t->state == 'n' && t->mtime != -1) {
613 continue;
724 continue;
614 }
725 }
615 if (PySet_Add(nonnset, fname) == -1) {
726 if (PySet_Add(nonnset, fname) == -1) {
616 goto bail;
727 goto bail;
617 }
728 }
618 }
729 }
619
730
620 result = Py_BuildValue("(OO)", nonnset, otherpset);
731 result = Py_BuildValue("(OO)", nonnset, otherpset);
621 if (result == NULL) {
732 if (result == NULL) {
622 goto bail;
733 goto bail;
623 }
734 }
624 Py_DECREF(nonnset);
735 Py_DECREF(nonnset);
625 Py_DECREF(otherpset);
736 Py_DECREF(otherpset);
626 return result;
737 return result;
627 bail:
738 bail:
628 Py_XDECREF(nonnset);
739 Py_XDECREF(nonnset);
629 Py_XDECREF(otherpset);
740 Py_XDECREF(otherpset);
630 Py_XDECREF(result);
741 Py_XDECREF(result);
631 return NULL;
742 return NULL;
632 }
743 }
633
744
634 /*
745 /*
635 * Efficiently pack a dirstate object into its on-disk format.
746 * Efficiently pack a dirstate object into its on-disk format.
636 */
747 */
637 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
748 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
638 {
749 {
639 PyObject *packobj = NULL;
750 PyObject *packobj = NULL;
640 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
751 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
641 Py_ssize_t nbytes, pos, l;
752 Py_ssize_t nbytes, pos, l;
642 PyObject *k, *v = NULL, *pn;
753 PyObject *k, *v = NULL, *pn;
643 char *p, *s;
754 char *p, *s;
644 int now;
755 int now;
645
756
646 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
757 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
647 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
758 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
648 &now)) {
759 &now)) {
649 return NULL;
760 return NULL;
650 }
761 }
651
762
652 if (PyTuple_Size(pl) != 2) {
763 if (PyTuple_Size(pl) != 2) {
653 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
764 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
654 return NULL;
765 return NULL;
655 }
766 }
656
767
657 /* Figure out how much we need to allocate. */
768 /* Figure out how much we need to allocate. */
658 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
769 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
659 PyObject *c;
770 PyObject *c;
660 if (!PyBytes_Check(k)) {
771 if (!PyBytes_Check(k)) {
661 PyErr_SetString(PyExc_TypeError, "expected string key");
772 PyErr_SetString(PyExc_TypeError, "expected string key");
662 goto bail;
773 goto bail;
663 }
774 }
664 nbytes += PyBytes_GET_SIZE(k) + 17;
775 nbytes += PyBytes_GET_SIZE(k) + 17;
665 c = PyDict_GetItem(copymap, k);
776 c = PyDict_GetItem(copymap, k);
666 if (c) {
777 if (c) {
667 if (!PyBytes_Check(c)) {
778 if (!PyBytes_Check(c)) {
668 PyErr_SetString(PyExc_TypeError,
779 PyErr_SetString(PyExc_TypeError,
669 "expected string key");
780 "expected string key");
670 goto bail;
781 goto bail;
671 }
782 }
672 nbytes += PyBytes_GET_SIZE(c) + 1;
783 nbytes += PyBytes_GET_SIZE(c) + 1;
673 }
784 }
674 }
785 }
675
786
676 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
787 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
677 if (packobj == NULL) {
788 if (packobj == NULL) {
678 goto bail;
789 goto bail;
679 }
790 }
680
791
681 p = PyBytes_AS_STRING(packobj);
792 p = PyBytes_AS_STRING(packobj);
682
793
683 pn = PyTuple_GET_ITEM(pl, 0);
794 pn = PyTuple_GET_ITEM(pl, 0);
684 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
795 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
685 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
796 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
686 goto bail;
797 goto bail;
687 }
798 }
688 memcpy(p, s, l);
799 memcpy(p, s, l);
689 p += 20;
800 p += 20;
690 pn = PyTuple_GET_ITEM(pl, 1);
801 pn = PyTuple_GET_ITEM(pl, 1);
691 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
802 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
692 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
803 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
693 goto bail;
804 goto bail;
694 }
805 }
695 memcpy(p, s, l);
806 memcpy(p, s, l);
696 p += 20;
807 p += 20;
697
808
698 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
809 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
699 dirstateItemObject *tuple;
810 dirstateItemObject *tuple;
700 char state;
811 char state;
701 int mode, size, mtime;
812 int mode, size, mtime;
702 Py_ssize_t len, l;
813 Py_ssize_t len, l;
703 PyObject *o;
814 PyObject *o;
704 char *t;
815 char *t;
705
816
706 if (!dirstate_tuple_check(v)) {
817 if (!dirstate_tuple_check(v)) {
707 PyErr_SetString(PyExc_TypeError,
818 PyErr_SetString(PyExc_TypeError,
708 "expected a dirstate tuple");
819 "expected a dirstate tuple");
709 goto bail;
820 goto bail;
710 }
821 }
711 tuple = (dirstateItemObject *)v;
822 tuple = (dirstateItemObject *)v;
712
823
713 state = tuple->state;
824 state = tuple->state;
714 mode = tuple->mode;
825 mode = tuple->mode;
715 size = tuple->size;
826 size = tuple->size;
716 mtime = tuple->mtime;
827 mtime = tuple->mtime;
717 if (state == 'n' && mtime == now) {
828 if (state == 'n' && mtime == now) {
718 /* See pure/parsers.py:pack_dirstate for why we do
829 /* See pure/parsers.py:pack_dirstate for why we do
719 * this. */
830 * this. */
720 mtime = -1;
831 mtime = -1;
721 mtime_unset = (PyObject *)make_dirstate_item(
832 mtime_unset = (PyObject *)make_dirstate_item(
722 state, mode, size, mtime);
833 state, mode, size, mtime);
723 if (!mtime_unset) {
834 if (!mtime_unset) {
724 goto bail;
835 goto bail;
725 }
836 }
726 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
837 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
727 goto bail;
838 goto bail;
728 }
839 }
729 Py_DECREF(mtime_unset);
840 Py_DECREF(mtime_unset);
730 mtime_unset = NULL;
841 mtime_unset = NULL;
731 }
842 }
732 *p++ = state;
843 *p++ = state;
733 putbe32((uint32_t)mode, p);
844 putbe32((uint32_t)mode, p);
734 putbe32((uint32_t)size, p + 4);
845 putbe32((uint32_t)size, p + 4);
735 putbe32((uint32_t)mtime, p + 8);
846 putbe32((uint32_t)mtime, p + 8);
736 t = p + 12;
847 t = p + 12;
737 p += 16;
848 p += 16;
738 len = PyBytes_GET_SIZE(k);
849 len = PyBytes_GET_SIZE(k);
739 memcpy(p, PyBytes_AS_STRING(k), len);
850 memcpy(p, PyBytes_AS_STRING(k), len);
740 p += len;
851 p += len;
741 o = PyDict_GetItem(copymap, k);
852 o = PyDict_GetItem(copymap, k);
742 if (o) {
853 if (o) {
743 *p++ = '\0';
854 *p++ = '\0';
744 l = PyBytes_GET_SIZE(o);
855 l = PyBytes_GET_SIZE(o);
745 memcpy(p, PyBytes_AS_STRING(o), l);
856 memcpy(p, PyBytes_AS_STRING(o), l);
746 p += l;
857 p += l;
747 len += l + 1;
858 len += l + 1;
748 }
859 }
749 putbe32((uint32_t)len, t);
860 putbe32((uint32_t)len, t);
750 }
861 }
751
862
752 pos = p - PyBytes_AS_STRING(packobj);
863 pos = p - PyBytes_AS_STRING(packobj);
753 if (pos != nbytes) {
864 if (pos != nbytes) {
754 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
865 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
755 (long)pos, (long)nbytes);
866 (long)pos, (long)nbytes);
756 goto bail;
867 goto bail;
757 }
868 }
758
869
759 return packobj;
870 return packobj;
760 bail:
871 bail:
761 Py_XDECREF(mtime_unset);
872 Py_XDECREF(mtime_unset);
762 Py_XDECREF(packobj);
873 Py_XDECREF(packobj);
763 Py_XDECREF(v);
874 Py_XDECREF(v);
764 return NULL;
875 return NULL;
765 }
876 }
766
877
767 #define BUMPED_FIX 1
878 #define BUMPED_FIX 1
768 #define USING_SHA_256 2
879 #define USING_SHA_256 2
769 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
880 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
770
881
771 static PyObject *readshas(const char *source, unsigned char num,
882 static PyObject *readshas(const char *source, unsigned char num,
772 Py_ssize_t hashwidth)
883 Py_ssize_t hashwidth)
773 {
884 {
774 int i;
885 int i;
775 PyObject *list = PyTuple_New(num);
886 PyObject *list = PyTuple_New(num);
776 if (list == NULL) {
887 if (list == NULL) {
777 return NULL;
888 return NULL;
778 }
889 }
779 for (i = 0; i < num; i++) {
890 for (i = 0; i < num; i++) {
780 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
891 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
781 if (hash == NULL) {
892 if (hash == NULL) {
782 Py_DECREF(list);
893 Py_DECREF(list);
783 return NULL;
894 return NULL;
784 }
895 }
785 PyTuple_SET_ITEM(list, i, hash);
896 PyTuple_SET_ITEM(list, i, hash);
786 source += hashwidth;
897 source += hashwidth;
787 }
898 }
788 return list;
899 return list;
789 }
900 }
790
901
791 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
902 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
792 uint32_t *msize)
903 uint32_t *msize)
793 {
904 {
794 const char *data = databegin;
905 const char *data = databegin;
795 const char *meta;
906 const char *meta;
796
907
797 double mtime;
908 double mtime;
798 int16_t tz;
909 int16_t tz;
799 uint16_t flags;
910 uint16_t flags;
800 unsigned char nsuccs, nparents, nmetadata;
911 unsigned char nsuccs, nparents, nmetadata;
801 Py_ssize_t hashwidth = 20;
912 Py_ssize_t hashwidth = 20;
802
913
803 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
914 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
804 PyObject *metadata = NULL, *ret = NULL;
915 PyObject *metadata = NULL, *ret = NULL;
805 int i;
916 int i;
806
917
807 if (data + FM1_HEADER_SIZE > dataend) {
918 if (data + FM1_HEADER_SIZE > dataend) {
808 goto overflow;
919 goto overflow;
809 }
920 }
810
921
811 *msize = getbe32(data);
922 *msize = getbe32(data);
812 data += 4;
923 data += 4;
813 mtime = getbefloat64(data);
924 mtime = getbefloat64(data);
814 data += 8;
925 data += 8;
815 tz = getbeint16(data);
926 tz = getbeint16(data);
816 data += 2;
927 data += 2;
817 flags = getbeuint16(data);
928 flags = getbeuint16(data);
818 data += 2;
929 data += 2;
819
930
820 if (flags & USING_SHA_256) {
931 if (flags & USING_SHA_256) {
821 hashwidth = 32;
932 hashwidth = 32;
822 }
933 }
823
934
824 nsuccs = (unsigned char)(*data++);
935 nsuccs = (unsigned char)(*data++);
825 nparents = (unsigned char)(*data++);
936 nparents = (unsigned char)(*data++);
826 nmetadata = (unsigned char)(*data++);
937 nmetadata = (unsigned char)(*data++);
827
938
828 if (databegin + *msize > dataend) {
939 if (databegin + *msize > dataend) {
829 goto overflow;
940 goto overflow;
830 }
941 }
831 dataend = databegin + *msize; /* narrow down to marker size */
942 dataend = databegin + *msize; /* narrow down to marker size */
832
943
833 if (data + hashwidth > dataend) {
944 if (data + hashwidth > dataend) {
834 goto overflow;
945 goto overflow;
835 }
946 }
836 prec = PyBytes_FromStringAndSize(data, hashwidth);
947 prec = PyBytes_FromStringAndSize(data, hashwidth);
837 data += hashwidth;
948 data += hashwidth;
838 if (prec == NULL) {
949 if (prec == NULL) {
839 goto bail;
950 goto bail;
840 }
951 }
841
952
842 if (data + nsuccs * hashwidth > dataend) {
953 if (data + nsuccs * hashwidth > dataend) {
843 goto overflow;
954 goto overflow;
844 }
955 }
845 succs = readshas(data, nsuccs, hashwidth);
956 succs = readshas(data, nsuccs, hashwidth);
846 if (succs == NULL) {
957 if (succs == NULL) {
847 goto bail;
958 goto bail;
848 }
959 }
849 data += nsuccs * hashwidth;
960 data += nsuccs * hashwidth;
850
961
851 if (nparents == 1 || nparents == 2) {
962 if (nparents == 1 || nparents == 2) {
852 if (data + nparents * hashwidth > dataend) {
963 if (data + nparents * hashwidth > dataend) {
853 goto overflow;
964 goto overflow;
854 }
965 }
855 parents = readshas(data, nparents, hashwidth);
966 parents = readshas(data, nparents, hashwidth);
856 if (parents == NULL) {
967 if (parents == NULL) {
857 goto bail;
968 goto bail;
858 }
969 }
859 data += nparents * hashwidth;
970 data += nparents * hashwidth;
860 } else {
971 } else {
861 parents = Py_None;
972 parents = Py_None;
862 Py_INCREF(parents);
973 Py_INCREF(parents);
863 }
974 }
864
975
865 if (data + 2 * nmetadata > dataend) {
976 if (data + 2 * nmetadata > dataend) {
866 goto overflow;
977 goto overflow;
867 }
978 }
868 meta = data + (2 * nmetadata);
979 meta = data + (2 * nmetadata);
869 metadata = PyTuple_New(nmetadata);
980 metadata = PyTuple_New(nmetadata);
870 if (metadata == NULL) {
981 if (metadata == NULL) {
871 goto bail;
982 goto bail;
872 }
983 }
873 for (i = 0; i < nmetadata; i++) {
984 for (i = 0; i < nmetadata; i++) {
874 PyObject *tmp, *left = NULL, *right = NULL;
985 PyObject *tmp, *left = NULL, *right = NULL;
875 Py_ssize_t leftsize = (unsigned char)(*data++);
986 Py_ssize_t leftsize = (unsigned char)(*data++);
876 Py_ssize_t rightsize = (unsigned char)(*data++);
987 Py_ssize_t rightsize = (unsigned char)(*data++);
877 if (meta + leftsize + rightsize > dataend) {
988 if (meta + leftsize + rightsize > dataend) {
878 goto overflow;
989 goto overflow;
879 }
990 }
880 left = PyBytes_FromStringAndSize(meta, leftsize);
991 left = PyBytes_FromStringAndSize(meta, leftsize);
881 meta += leftsize;
992 meta += leftsize;
882 right = PyBytes_FromStringAndSize(meta, rightsize);
993 right = PyBytes_FromStringAndSize(meta, rightsize);
883 meta += rightsize;
994 meta += rightsize;
884 tmp = PyTuple_New(2);
995 tmp = PyTuple_New(2);
885 if (!left || !right || !tmp) {
996 if (!left || !right || !tmp) {
886 Py_XDECREF(left);
997 Py_XDECREF(left);
887 Py_XDECREF(right);
998 Py_XDECREF(right);
888 Py_XDECREF(tmp);
999 Py_XDECREF(tmp);
889 goto bail;
1000 goto bail;
890 }
1001 }
891 PyTuple_SET_ITEM(tmp, 0, left);
1002 PyTuple_SET_ITEM(tmp, 0, left);
892 PyTuple_SET_ITEM(tmp, 1, right);
1003 PyTuple_SET_ITEM(tmp, 1, right);
893 PyTuple_SET_ITEM(metadata, i, tmp);
1004 PyTuple_SET_ITEM(metadata, i, tmp);
894 }
1005 }
895 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1006 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
896 (int)tz * 60, parents);
1007 (int)tz * 60, parents);
897 goto bail; /* return successfully */
1008 goto bail; /* return successfully */
898
1009
899 overflow:
1010 overflow:
900 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1011 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
901 bail:
1012 bail:
902 Py_XDECREF(prec);
1013 Py_XDECREF(prec);
903 Py_XDECREF(succs);
1014 Py_XDECREF(succs);
904 Py_XDECREF(metadata);
1015 Py_XDECREF(metadata);
905 Py_XDECREF(parents);
1016 Py_XDECREF(parents);
906 return ret;
1017 return ret;
907 }
1018 }
908
1019
909 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1020 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
910 {
1021 {
911 const char *data, *dataend;
1022 const char *data, *dataend;
912 Py_ssize_t datalen, offset, stop;
1023 Py_ssize_t datalen, offset, stop;
913 PyObject *markers = NULL;
1024 PyObject *markers = NULL;
914
1025
915 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1026 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
916 &offset, &stop)) {
1027 &offset, &stop)) {
917 return NULL;
1028 return NULL;
918 }
1029 }
919 if (offset < 0) {
1030 if (offset < 0) {
920 PyErr_SetString(PyExc_ValueError,
1031 PyErr_SetString(PyExc_ValueError,
921 "invalid negative offset in fm1readmarkers");
1032 "invalid negative offset in fm1readmarkers");
922 return NULL;
1033 return NULL;
923 }
1034 }
924 if (stop > datalen) {
1035 if (stop > datalen) {
925 PyErr_SetString(
1036 PyErr_SetString(
926 PyExc_ValueError,
1037 PyExc_ValueError,
927 "stop longer than data length in fm1readmarkers");
1038 "stop longer than data length in fm1readmarkers");
928 return NULL;
1039 return NULL;
929 }
1040 }
930 dataend = data + datalen;
1041 dataend = data + datalen;
931 data += offset;
1042 data += offset;
932 markers = PyList_New(0);
1043 markers = PyList_New(0);
933 if (!markers) {
1044 if (!markers) {
934 return NULL;
1045 return NULL;
935 }
1046 }
936 while (offset < stop) {
1047 while (offset < stop) {
937 uint32_t msize;
1048 uint32_t msize;
938 int error;
1049 int error;
939 PyObject *record = fm1readmarker(data, dataend, &msize);
1050 PyObject *record = fm1readmarker(data, dataend, &msize);
940 if (!record) {
1051 if (!record) {
941 goto bail;
1052 goto bail;
942 }
1053 }
943 error = PyList_Append(markers, record);
1054 error = PyList_Append(markers, record);
944 Py_DECREF(record);
1055 Py_DECREF(record);
945 if (error) {
1056 if (error) {
946 goto bail;
1057 goto bail;
947 }
1058 }
948 data += msize;
1059 data += msize;
949 offset += msize;
1060 offset += msize;
950 }
1061 }
951 return markers;
1062 return markers;
952 bail:
1063 bail:
953 Py_DECREF(markers);
1064 Py_DECREF(markers);
954 return NULL;
1065 return NULL;
955 }
1066 }
956
1067
957 static char parsers_doc[] = "Efficient content parsing.";
1068 static char parsers_doc[] = "Efficient content parsing.";
958
1069
959 PyObject *encodedir(PyObject *self, PyObject *args);
1070 PyObject *encodedir(PyObject *self, PyObject *args);
960 PyObject *pathencode(PyObject *self, PyObject *args);
1071 PyObject *pathencode(PyObject *self, PyObject *args);
961 PyObject *lowerencode(PyObject *self, PyObject *args);
1072 PyObject *lowerencode(PyObject *self, PyObject *args);
962 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1073 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
963
1074
964 static PyMethodDef methods[] = {
1075 static PyMethodDef methods[] = {
965 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1076 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
966 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
1077 {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
967 "create a set containing non-normal and other parent entries of given "
1078 "create a set containing non-normal and other parent entries of given "
968 "dirstate\n"},
1079 "dirstate\n"},
969 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1080 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
970 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1081 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
971 "parse a revlog index\n"},
1082 "parse a revlog index\n"},
972 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1083 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
973 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1084 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
974 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1085 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
975 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1086 {"dict_new_presized", dict_new_presized, METH_VARARGS,
976 "construct a dict with an expected size\n"},
1087 "construct a dict with an expected size\n"},
977 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1088 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
978 "make file foldmap\n"},
1089 "make file foldmap\n"},
979 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1090 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
980 "escape a UTF-8 byte string to JSON (fast path)\n"},
1091 "escape a UTF-8 byte string to JSON (fast path)\n"},
981 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1092 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
982 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1093 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
983 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1094 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
984 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1095 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
985 "parse v1 obsolete markers\n"},
1096 "parse v1 obsolete markers\n"},
986 {NULL, NULL}};
1097 {NULL, NULL}};
987
1098
988 void dirs_module_init(PyObject *mod);
1099 void dirs_module_init(PyObject *mod);
989 void manifest_module_init(PyObject *mod);
1100 void manifest_module_init(PyObject *mod);
990 void revlog_module_init(PyObject *mod);
1101 void revlog_module_init(PyObject *mod);
991
1102
992 static const int version = 20;
1103 static const int version = 20;
993
1104
994 static void module_init(PyObject *mod)
1105 static void module_init(PyObject *mod)
995 {
1106 {
996 PyObject *capsule = NULL;
1107 PyObject *capsule = NULL;
997 PyModule_AddIntConstant(mod, "version", version);
1108 PyModule_AddIntConstant(mod, "version", version);
998
1109
999 /* This module constant has two purposes. First, it lets us unit test
1110 /* This module constant has two purposes. First, it lets us unit test
1000 * the ImportError raised without hard-coding any error text. This
1111 * the ImportError raised without hard-coding any error text. This
1001 * means we can change the text in the future without breaking tests,
1112 * means we can change the text in the future without breaking tests,
1002 * even across changesets without a recompile. Second, its presence
1113 * even across changesets without a recompile. Second, its presence
1003 * can be used to determine whether the version-checking logic is
1114 * can be used to determine whether the version-checking logic is
1004 * present, which also helps in testing across changesets without a
1115 * present, which also helps in testing across changesets without a
1005 * recompile. Note that this means the pure-Python version of parsers
1116 * recompile. Note that this means the pure-Python version of parsers
1006 * should not have this module constant. */
1117 * should not have this module constant. */
1007 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1118 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1008
1119
1009 dirs_module_init(mod);
1120 dirs_module_init(mod);
1010 manifest_module_init(mod);
1121 manifest_module_init(mod);
1011 revlog_module_init(mod);
1122 revlog_module_init(mod);
1012
1123
1013 capsule = PyCapsule_New(
1124 capsule = PyCapsule_New(
1014 make_dirstate_item,
1125 make_dirstate_item,
1015 "mercurial.cext.parsers.make_dirstate_item_CAPI", NULL);
1126 "mercurial.cext.parsers.make_dirstate_item_CAPI", NULL);
1016 if (capsule != NULL)
1127 if (capsule != NULL)
1017 PyModule_AddObject(mod, "make_dirstate_item_CAPI", capsule);
1128 PyModule_AddObject(mod, "make_dirstate_item_CAPI", capsule);
1018
1129
1019 if (PyType_Ready(&dirstateItemType) < 0) {
1130 if (PyType_Ready(&dirstateItemType) < 0) {
1020 return;
1131 return;
1021 }
1132 }
1022 Py_INCREF(&dirstateItemType);
1133 Py_INCREF(&dirstateItemType);
1023 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1134 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1024 }
1135 }
1025
1136
1026 static int check_python_version(void)
1137 static int check_python_version(void)
1027 {
1138 {
1028 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1139 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1029 long hexversion;
1140 long hexversion;
1030 if (!sys) {
1141 if (!sys) {
1031 return -1;
1142 return -1;
1032 }
1143 }
1033 ver = PyObject_GetAttrString(sys, "hexversion");
1144 ver = PyObject_GetAttrString(sys, "hexversion");
1034 Py_DECREF(sys);
1145 Py_DECREF(sys);
1035 if (!ver) {
1146 if (!ver) {
1036 return -1;
1147 return -1;
1037 }
1148 }
1038 hexversion = PyInt_AsLong(ver);
1149 hexversion = PyInt_AsLong(ver);
1039 Py_DECREF(ver);
1150 Py_DECREF(ver);
1040 /* sys.hexversion is a 32-bit number by default, so the -1 case
1151 /* sys.hexversion is a 32-bit number by default, so the -1 case
1041 * should only occur in unusual circumstances (e.g. if sys.hexversion
1152 * should only occur in unusual circumstances (e.g. if sys.hexversion
1042 * is manually set to an invalid value). */
1153 * is manually set to an invalid value). */
1043 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1154 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1044 PyErr_Format(PyExc_ImportError,
1155 PyErr_Format(PyExc_ImportError,
1045 "%s: The Mercurial extension "
1156 "%s: The Mercurial extension "
1046 "modules were compiled with Python " PY_VERSION
1157 "modules were compiled with Python " PY_VERSION
1047 ", but "
1158 ", but "
1048 "Mercurial is currently using Python with "
1159 "Mercurial is currently using Python with "
1049 "sys.hexversion=%ld: "
1160 "sys.hexversion=%ld: "
1050 "Python %s\n at: %s",
1161 "Python %s\n at: %s",
1051 versionerrortext, hexversion, Py_GetVersion(),
1162 versionerrortext, hexversion, Py_GetVersion(),
1052 Py_GetProgramFullPath());
1163 Py_GetProgramFullPath());
1053 return -1;
1164 return -1;
1054 }
1165 }
1055 return 0;
1166 return 0;
1056 }
1167 }
1057
1168
1058 #ifdef IS_PY3K
1169 #ifdef IS_PY3K
1059 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1170 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1060 parsers_doc, -1, methods};
1171 parsers_doc, -1, methods};
1061
1172
1062 PyMODINIT_FUNC PyInit_parsers(void)
1173 PyMODINIT_FUNC PyInit_parsers(void)
1063 {
1174 {
1064 PyObject *mod;
1175 PyObject *mod;
1065
1176
1066 if (check_python_version() == -1)
1177 if (check_python_version() == -1)
1067 return NULL;
1178 return NULL;
1068 mod = PyModule_Create(&parsers_module);
1179 mod = PyModule_Create(&parsers_module);
1069 module_init(mod);
1180 module_init(mod);
1070 return mod;
1181 return mod;
1071 }
1182 }
1072 #else
1183 #else
1073 PyMODINIT_FUNC initparsers(void)
1184 PyMODINIT_FUNC initparsers(void)
1074 {
1185 {
1075 PyObject *mod;
1186 PyObject *mod;
1076
1187
1077 if (check_python_version() == -1) {
1188 if (check_python_version() == -1) {
1078 return;
1189 return;
1079 }
1190 }
1080 mod = Py_InitModule3("parsers", methods, parsers_doc);
1191 mod = Py_InitModule3("parsers", methods, parsers_doc);
1081 module_init(mod);
1192 module_init(mod);
1082 }
1193 }
1083 #endif
1194 #endif
@@ -1,930 +1,916 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
32
33 # a special value used internally for `size` if the file come from the other parent
33 # a special value used internally for `size` if the file come from the other parent
34 FROM_P2 = -2
34 FROM_P2 = -2
35
35
36 # a special value used internally for `size` if the file is modified/merged/added
36 # a special value used internally for `size` if the file is modified/merged/added
37 NONNORMAL = -1
37 NONNORMAL = -1
38
38
39 # a special value used internally for `time` if the time is ambigeous
39 # a special value used internally for `time` if the time is ambigeous
40 AMBIGUOUS_TIME = -1
40 AMBIGUOUS_TIME = -1
41
41
42 rangemask = 0x7FFFFFFF
42 rangemask = 0x7FFFFFFF
43
43
44
44
45 class dirstatemap(object):
45 class dirstatemap(object):
46 """Map encapsulating the dirstate's contents.
46 """Map encapsulating the dirstate's contents.
47
47
48 The dirstate contains the following state:
48 The dirstate contains the following state:
49
49
50 - `identity` is the identity of the dirstate file, which can be used to
50 - `identity` is the identity of the dirstate file, which can be used to
51 detect when changes have occurred to the dirstate file.
51 detect when changes have occurred to the dirstate file.
52
52
53 - `parents` is a pair containing the parents of the working copy. The
53 - `parents` is a pair containing the parents of the working copy. The
54 parents are updated by calling `setparents`.
54 parents are updated by calling `setparents`.
55
55
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
56 - the state map maps filenames to tuples of (state, mode, size, mtime),
57 where state is a single character representing 'normal', 'added',
57 where state is a single character representing 'normal', 'added',
58 'removed', or 'merged'. It is read by treating the dirstate as a
58 'removed', or 'merged'. It is read by treating the dirstate as a
59 dict. File state is updated by calling the `addfile`, `removefile` and
59 dict. File state is updated by calling the `addfile`, `removefile` and
60 `dropfile` methods.
60 `dropfile` methods.
61
61
62 - `copymap` maps destination filenames to their source filename.
62 - `copymap` maps destination filenames to their source filename.
63
63
64 The dirstate also provides the following views onto the state:
64 The dirstate also provides the following views onto the state:
65
65
66 - `nonnormalset` is a set of the filenames that have state other
66 - `nonnormalset` is a set of the filenames that have state other
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
67 than 'normal', or are normal but have an mtime of -1 ('normallookup').
68
68
69 - `otherparentset` is a set of the filenames that are marked as coming
69 - `otherparentset` is a set of the filenames that are marked as coming
70 from the second parent when the dirstate is currently being merged.
70 from the second parent when the dirstate is currently being merged.
71
71
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
72 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
73 form that they appear as in the dirstate.
73 form that they appear as in the dirstate.
74
74
75 - `dirfoldmap` is a dict mapping normalized directory names to the
75 - `dirfoldmap` is a dict mapping normalized directory names to the
76 denormalized form that they appear as in the dirstate.
76 denormalized form that they appear as in the dirstate.
77 """
77 """
78
78
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
79 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
80 self._ui = ui
80 self._ui = ui
81 self._opener = opener
81 self._opener = opener
82 self._root = root
82 self._root = root
83 self._filename = b'dirstate'
83 self._filename = b'dirstate'
84 self._nodelen = 20
84 self._nodelen = 20
85 self._nodeconstants = nodeconstants
85 self._nodeconstants = nodeconstants
86 assert (
86 assert (
87 not use_dirstate_v2
87 not use_dirstate_v2
88 ), "should have detected unsupported requirement"
88 ), "should have detected unsupported requirement"
89
89
90 self._parents = None
90 self._parents = None
91 self._dirtyparents = False
91 self._dirtyparents = False
92
92
93 # for consistent view between _pl() and _read() invocations
93 # for consistent view between _pl() and _read() invocations
94 self._pendingmode = None
94 self._pendingmode = None
95
95
96 @propertycache
96 @propertycache
97 def _map(self):
97 def _map(self):
98 self._map = {}
98 self._map = {}
99 self.read()
99 self.read()
100 return self._map
100 return self._map
101
101
102 @propertycache
102 @propertycache
103 def copymap(self):
103 def copymap(self):
104 self.copymap = {}
104 self.copymap = {}
105 self._map
105 self._map
106 return self.copymap
106 return self.copymap
107
107
108 def clear(self):
108 def clear(self):
109 self._map.clear()
109 self._map.clear()
110 self.copymap.clear()
110 self.copymap.clear()
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
111 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
112 util.clearcachedproperty(self, b"_dirs")
112 util.clearcachedproperty(self, b"_dirs")
113 util.clearcachedproperty(self, b"_alldirs")
113 util.clearcachedproperty(self, b"_alldirs")
114 util.clearcachedproperty(self, b"filefoldmap")
114 util.clearcachedproperty(self, b"filefoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
115 util.clearcachedproperty(self, b"dirfoldmap")
116 util.clearcachedproperty(self, b"nonnormalset")
116 util.clearcachedproperty(self, b"nonnormalset")
117 util.clearcachedproperty(self, b"otherparentset")
117 util.clearcachedproperty(self, b"otherparentset")
118
118
119 def items(self):
119 def items(self):
120 return pycompat.iteritems(self._map)
120 return pycompat.iteritems(self._map)
121
121
122 # forward for python2,3 compat
122 # forward for python2,3 compat
123 iteritems = items
123 iteritems = items
124
124
125 debug_iter = items
125 debug_iter = items
126
126
127 def __len__(self):
127 def __len__(self):
128 return len(self._map)
128 return len(self._map)
129
129
130 def __iter__(self):
130 def __iter__(self):
131 return iter(self._map)
131 return iter(self._map)
132
132
133 def get(self, key, default=None):
133 def get(self, key, default=None):
134 return self._map.get(key, default)
134 return self._map.get(key, default)
135
135
136 def __contains__(self, key):
136 def __contains__(self, key):
137 return key in self._map
137 return key in self._map
138
138
139 def __getitem__(self, key):
139 def __getitem__(self, key):
140 return self._map[key]
140 return self._map[key]
141
141
142 def keys(self):
142 def keys(self):
143 return self._map.keys()
143 return self._map.keys()
144
144
145 def preload(self):
145 def preload(self):
146 """Loads the underlying data, if it's not already loaded"""
146 """Loads the underlying data, if it's not already loaded"""
147 self._map
147 self._map
148
148
149 def _dirs_incr(self, filename, old_entry=None):
149 def _dirs_incr(self, filename, old_entry=None):
150 """incremente the dirstate counter if applicable"""
150 """incremente the dirstate counter if applicable"""
151 if (
151 if (
152 old_entry is None or old_entry.removed
152 old_entry is None or old_entry.removed
153 ) and "_dirs" in self.__dict__:
153 ) and "_dirs" in self.__dict__:
154 self._dirs.addpath(filename)
154 self._dirs.addpath(filename)
155 if old_entry is None and "_alldirs" in self.__dict__:
155 if old_entry is None and "_alldirs" in self.__dict__:
156 self._alldirs.addpath(filename)
156 self._alldirs.addpath(filename)
157
157
158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
158 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
159 """decremente the dirstate counter if applicable"""
159 """decremente the dirstate counter if applicable"""
160 if old_entry is not None:
160 if old_entry is not None:
161 if "_dirs" in self.__dict__ and not old_entry.removed:
161 if "_dirs" in self.__dict__ and not old_entry.removed:
162 self._dirs.delpath(filename)
162 self._dirs.delpath(filename)
163 if "_alldirs" in self.__dict__ and not remove_variant:
163 if "_alldirs" in self.__dict__ and not remove_variant:
164 self._alldirs.delpath(filename)
164 self._alldirs.delpath(filename)
165 elif remove_variant and "_alldirs" in self.__dict__:
165 elif remove_variant and "_alldirs" in self.__dict__:
166 self._alldirs.addpath(filename)
166 self._alldirs.addpath(filename)
167 if "filefoldmap" in self.__dict__:
167 if "filefoldmap" in self.__dict__:
168 normed = util.normcase(filename)
168 normed = util.normcase(filename)
169 self.filefoldmap.pop(normed, None)
169 self.filefoldmap.pop(normed, None)
170
170
171 def set_possibly_dirty(self, filename):
171 def set_possibly_dirty(self, filename):
172 """record that the current state of the file on disk is unknown"""
172 """record that the current state of the file on disk is unknown"""
173 self[filename].set_possibly_dirty()
173 self[filename].set_possibly_dirty()
174
174
175 def addfile(
175 def addfile(
176 self,
176 self,
177 f,
177 f,
178 mode=0,
178 mode=0,
179 size=None,
179 size=None,
180 mtime=None,
180 mtime=None,
181 added=False,
181 added=False,
182 merged=False,
182 merged=False,
183 from_p2=False,
183 from_p2=False,
184 possibly_dirty=False,
184 possibly_dirty=False,
185 ):
185 ):
186 """Add a tracked file to the dirstate."""
186 """Add a tracked file to the dirstate."""
187 if added:
187 if added:
188 assert not merged
188 assert not merged
189 assert not possibly_dirty
189 assert not possibly_dirty
190 assert not from_p2
190 assert not from_p2
191 state = b'a'
191 new_entry = DirstateItem.new_added()
192 size = NONNORMAL
193 mtime = AMBIGUOUS_TIME
194 elif merged:
192 elif merged:
195 assert not possibly_dirty
193 assert not possibly_dirty
196 assert not from_p2
194 assert not from_p2
197 state = b'm'
195 new_entry = DirstateItem.new_merged()
198 size = FROM_P2
199 mtime = AMBIGUOUS_TIME
200 elif from_p2:
196 elif from_p2:
201 assert not possibly_dirty
197 assert not possibly_dirty
202 state = b'n'
198 new_entry = DirstateItem.new_from_p2()
203 size = FROM_P2
204 mtime = AMBIGUOUS_TIME
205 elif possibly_dirty:
199 elif possibly_dirty:
206 state = b'n'
200 new_entry = DirstateItem.new_possibly_dirty()
207 size = NONNORMAL
208 mtime = AMBIGUOUS_TIME
209 else:
201 else:
210 assert size != FROM_P2
211 assert size != NONNORMAL
212 assert size is not None
202 assert size is not None
213 assert mtime is not None
203 assert mtime is not None
214
215 state = b'n'
216 size = size & rangemask
204 size = size & rangemask
217 mtime = mtime & rangemask
205 mtime = mtime & rangemask
218 assert state is not None
206 new_entry = DirstateItem.new_normal(mode, size, mtime)
219 assert size is not None
220 assert mtime is not None
221 old_entry = self.get(f)
207 old_entry = self.get(f)
222 self._dirs_incr(f, old_entry)
208 self._dirs_incr(f, old_entry)
223 e = self._map[f] = DirstateItem.from_v1_data(state, mode, size, mtime)
209 self._map[f] = new_entry
224 if e.dm_nonnormal:
210 if new_entry.dm_nonnormal:
225 self.nonnormalset.add(f)
211 self.nonnormalset.add(f)
226 if e.dm_otherparent:
212 if new_entry.dm_otherparent:
227 self.otherparentset.add(f)
213 self.otherparentset.add(f)
228
214
229 def reset_state(
215 def reset_state(
230 self,
216 self,
231 filename,
217 filename,
232 wc_tracked,
218 wc_tracked,
233 p1_tracked,
219 p1_tracked,
234 p2_tracked=False,
220 p2_tracked=False,
235 merged=False,
221 merged=False,
236 clean_p1=False,
222 clean_p1=False,
237 clean_p2=False,
223 clean_p2=False,
238 possibly_dirty=False,
224 possibly_dirty=False,
239 parentfiledata=None,
225 parentfiledata=None,
240 ):
226 ):
241 """Set a entry to a given state, diregarding all previous state
227 """Set a entry to a given state, diregarding all previous state
242
228
243 This is to be used by the part of the dirstate API dedicated to
229 This is to be used by the part of the dirstate API dedicated to
244 adjusting the dirstate after a update/merge.
230 adjusting the dirstate after a update/merge.
245
231
246 note: calling this might result to no entry existing at all if the
232 note: calling this might result to no entry existing at all if the
247 dirstate map does not see any point at having one for this file
233 dirstate map does not see any point at having one for this file
248 anymore.
234 anymore.
249 """
235 """
250 if merged and (clean_p1 or clean_p2):
236 if merged and (clean_p1 or clean_p2):
251 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
237 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
252 raise error.ProgrammingError(msg)
238 raise error.ProgrammingError(msg)
253 # copy information are now outdated
239 # copy information are now outdated
254 # (maybe new information should be in directly passed to this function)
240 # (maybe new information should be in directly passed to this function)
255 self.copymap.pop(filename, None)
241 self.copymap.pop(filename, None)
256
242
257 if not (p1_tracked or p2_tracked or wc_tracked):
243 if not (p1_tracked or p2_tracked or wc_tracked):
258 self.dropfile(filename)
244 self.dropfile(filename)
259 return
245 return
260 elif merged:
246 elif merged:
261 # XXX might be merged and removed ?
247 # XXX might be merged and removed ?
262 entry = self.get(filename)
248 entry = self.get(filename)
263 if entry is None or not entry.tracked:
249 if entry is None or not entry.tracked:
264 # XXX mostly replicate dirstate.other parent. We should get
250 # XXX mostly replicate dirstate.other parent. We should get
265 # the higher layer to pass us more reliable data where `merged`
251 # the higher layer to pass us more reliable data where `merged`
266 # actually mean merged. Dropping this clause will show failure
252 # actually mean merged. Dropping this clause will show failure
267 # in `test-graft.t`
253 # in `test-graft.t`
268 merged = False
254 merged = False
269 clean_p2 = True
255 clean_p2 = True
270 elif not (p1_tracked or p2_tracked) and wc_tracked:
256 elif not (p1_tracked or p2_tracked) and wc_tracked:
271 pass # file is added, nothing special to adjust
257 pass # file is added, nothing special to adjust
272 elif (p1_tracked or p2_tracked) and not wc_tracked:
258 elif (p1_tracked or p2_tracked) and not wc_tracked:
273 pass
259 pass
274 elif clean_p2 and wc_tracked:
260 elif clean_p2 and wc_tracked:
275 if p1_tracked or self.get(filename) is not None:
261 if p1_tracked or self.get(filename) is not None:
276 # XXX the `self.get` call is catching some case in
262 # XXX the `self.get` call is catching some case in
277 # `test-merge-remove.t` where the file is tracked in p1, the
263 # `test-merge-remove.t` where the file is tracked in p1, the
278 # p1_tracked argument is False.
264 # p1_tracked argument is False.
279 #
265 #
280 # In addition, this seems to be a case where the file is marked
266 # In addition, this seems to be a case where the file is marked
281 # as merged without actually being the result of a merge
267 # as merged without actually being the result of a merge
282 # action. So thing are not ideal here.
268 # action. So thing are not ideal here.
283 merged = True
269 merged = True
284 clean_p2 = False
270 clean_p2 = False
285 elif not p1_tracked and p2_tracked and wc_tracked:
271 elif not p1_tracked and p2_tracked and wc_tracked:
286 clean_p2 = True
272 clean_p2 = True
287 elif possibly_dirty:
273 elif possibly_dirty:
288 pass
274 pass
289 elif wc_tracked:
275 elif wc_tracked:
290 # this is a "normal" file
276 # this is a "normal" file
291 if parentfiledata is None:
277 if parentfiledata is None:
292 msg = b'failed to pass parentfiledata for a normal file: %s'
278 msg = b'failed to pass parentfiledata for a normal file: %s'
293 msg %= filename
279 msg %= filename
294 raise error.ProgrammingError(msg)
280 raise error.ProgrammingError(msg)
295 else:
281 else:
296 assert False, 'unreachable'
282 assert False, 'unreachable'
297
283
298 old_entry = self._map.get(filename)
284 old_entry = self._map.get(filename)
299 self._dirs_incr(filename, old_entry)
285 self._dirs_incr(filename, old_entry)
300 entry = DirstateItem(
286 entry = DirstateItem(
301 wc_tracked=wc_tracked,
287 wc_tracked=wc_tracked,
302 p1_tracked=p1_tracked,
288 p1_tracked=p1_tracked,
303 p2_tracked=p2_tracked,
289 p2_tracked=p2_tracked,
304 merged=merged,
290 merged=merged,
305 clean_p1=clean_p1,
291 clean_p1=clean_p1,
306 clean_p2=clean_p2,
292 clean_p2=clean_p2,
307 possibly_dirty=possibly_dirty,
293 possibly_dirty=possibly_dirty,
308 parentfiledata=parentfiledata,
294 parentfiledata=parentfiledata,
309 )
295 )
310 if entry.dm_nonnormal:
296 if entry.dm_nonnormal:
311 self.nonnormalset.add(filename)
297 self.nonnormalset.add(filename)
312 else:
298 else:
313 self.nonnormalset.discard(filename)
299 self.nonnormalset.discard(filename)
314 if entry.dm_otherparent:
300 if entry.dm_otherparent:
315 self.otherparentset.add(filename)
301 self.otherparentset.add(filename)
316 else:
302 else:
317 self.otherparentset.discard(filename)
303 self.otherparentset.discard(filename)
318 self._map[filename] = entry
304 self._map[filename] = entry
319
305
320 def set_untracked(self, f):
306 def set_untracked(self, f):
321 """Mark a file as no longer tracked in the dirstate map"""
307 """Mark a file as no longer tracked in the dirstate map"""
322 entry = self[f]
308 entry = self[f]
323 self._dirs_decr(f, old_entry=entry, remove_variant=True)
309 self._dirs_decr(f, old_entry=entry, remove_variant=True)
324 if entry.from_p2:
310 if entry.from_p2:
325 self.otherparentset.add(f)
311 self.otherparentset.add(f)
326 elif not entry.merged:
312 elif not entry.merged:
327 self.copymap.pop(f, None)
313 self.copymap.pop(f, None)
328 entry.set_untracked()
314 entry.set_untracked()
329 self.nonnormalset.add(f)
315 self.nonnormalset.add(f)
330
316
331 def dropfile(self, f):
317 def dropfile(self, f):
332 """
318 """
333 Remove a file from the dirstate. Returns True if the file was
319 Remove a file from the dirstate. Returns True if the file was
334 previously recorded.
320 previously recorded.
335 """
321 """
336 old_entry = self._map.pop(f, None)
322 old_entry = self._map.pop(f, None)
337 self._dirs_decr(f, old_entry=old_entry)
323 self._dirs_decr(f, old_entry=old_entry)
338 self.nonnormalset.discard(f)
324 self.nonnormalset.discard(f)
339 return old_entry is not None
325 return old_entry is not None
340
326
341 def clearambiguoustimes(self, files, now):
327 def clearambiguoustimes(self, files, now):
342 for f in files:
328 for f in files:
343 e = self.get(f)
329 e = self.get(f)
344 if e is not None and e.need_delay(now):
330 if e is not None and e.need_delay(now):
345 e.set_possibly_dirty()
331 e.set_possibly_dirty()
346 self.nonnormalset.add(f)
332 self.nonnormalset.add(f)
347
333
348 def nonnormalentries(self):
334 def nonnormalentries(self):
349 '''Compute the nonnormal dirstate entries from the dmap'''
335 '''Compute the nonnormal dirstate entries from the dmap'''
350 try:
336 try:
351 return parsers.nonnormalotherparententries(self._map)
337 return parsers.nonnormalotherparententries(self._map)
352 except AttributeError:
338 except AttributeError:
353 nonnorm = set()
339 nonnorm = set()
354 otherparent = set()
340 otherparent = set()
355 for fname, e in pycompat.iteritems(self._map):
341 for fname, e in pycompat.iteritems(self._map):
356 if e.dm_nonnormal:
342 if e.dm_nonnormal:
357 nonnorm.add(fname)
343 nonnorm.add(fname)
358 if e.from_p2:
344 if e.from_p2:
359 otherparent.add(fname)
345 otherparent.add(fname)
360 return nonnorm, otherparent
346 return nonnorm, otherparent
361
347
362 @propertycache
348 @propertycache
363 def filefoldmap(self):
349 def filefoldmap(self):
364 """Returns a dictionary mapping normalized case paths to their
350 """Returns a dictionary mapping normalized case paths to their
365 non-normalized versions.
351 non-normalized versions.
366 """
352 """
367 try:
353 try:
368 makefilefoldmap = parsers.make_file_foldmap
354 makefilefoldmap = parsers.make_file_foldmap
369 except AttributeError:
355 except AttributeError:
370 pass
356 pass
371 else:
357 else:
372 return makefilefoldmap(
358 return makefilefoldmap(
373 self._map, util.normcasespec, util.normcasefallback
359 self._map, util.normcasespec, util.normcasefallback
374 )
360 )
375
361
376 f = {}
362 f = {}
377 normcase = util.normcase
363 normcase = util.normcase
378 for name, s in pycompat.iteritems(self._map):
364 for name, s in pycompat.iteritems(self._map):
379 if not s.removed:
365 if not s.removed:
380 f[normcase(name)] = name
366 f[normcase(name)] = name
381 f[b'.'] = b'.' # prevents useless util.fspath() invocation
367 f[b'.'] = b'.' # prevents useless util.fspath() invocation
382 return f
368 return f
383
369
384 def hastrackeddir(self, d):
370 def hastrackeddir(self, d):
385 """
371 """
386 Returns True if the dirstate contains a tracked (not removed) file
372 Returns True if the dirstate contains a tracked (not removed) file
387 in this directory.
373 in this directory.
388 """
374 """
389 return d in self._dirs
375 return d in self._dirs
390
376
391 def hasdir(self, d):
377 def hasdir(self, d):
392 """
378 """
393 Returns True if the dirstate contains a file (tracked or removed)
379 Returns True if the dirstate contains a file (tracked or removed)
394 in this directory.
380 in this directory.
395 """
381 """
396 return d in self._alldirs
382 return d in self._alldirs
397
383
398 @propertycache
384 @propertycache
399 def _dirs(self):
385 def _dirs(self):
400 return pathutil.dirs(self._map, b'r')
386 return pathutil.dirs(self._map, b'r')
401
387
402 @propertycache
388 @propertycache
403 def _alldirs(self):
389 def _alldirs(self):
404 return pathutil.dirs(self._map)
390 return pathutil.dirs(self._map)
405
391
406 def _opendirstatefile(self):
392 def _opendirstatefile(self):
407 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
393 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
408 if self._pendingmode is not None and self._pendingmode != mode:
394 if self._pendingmode is not None and self._pendingmode != mode:
409 fp.close()
395 fp.close()
410 raise error.Abort(
396 raise error.Abort(
411 _(b'working directory state may be changed parallelly')
397 _(b'working directory state may be changed parallelly')
412 )
398 )
413 self._pendingmode = mode
399 self._pendingmode = mode
414 return fp
400 return fp
415
401
416 def parents(self):
402 def parents(self):
417 if not self._parents:
403 if not self._parents:
418 try:
404 try:
419 fp = self._opendirstatefile()
405 fp = self._opendirstatefile()
420 st = fp.read(2 * self._nodelen)
406 st = fp.read(2 * self._nodelen)
421 fp.close()
407 fp.close()
422 except IOError as err:
408 except IOError as err:
423 if err.errno != errno.ENOENT:
409 if err.errno != errno.ENOENT:
424 raise
410 raise
425 # File doesn't exist, so the current state is empty
411 # File doesn't exist, so the current state is empty
426 st = b''
412 st = b''
427
413
428 l = len(st)
414 l = len(st)
429 if l == self._nodelen * 2:
415 if l == self._nodelen * 2:
430 self._parents = (
416 self._parents = (
431 st[: self._nodelen],
417 st[: self._nodelen],
432 st[self._nodelen : 2 * self._nodelen],
418 st[self._nodelen : 2 * self._nodelen],
433 )
419 )
434 elif l == 0:
420 elif l == 0:
435 self._parents = (
421 self._parents = (
436 self._nodeconstants.nullid,
422 self._nodeconstants.nullid,
437 self._nodeconstants.nullid,
423 self._nodeconstants.nullid,
438 )
424 )
439 else:
425 else:
440 raise error.Abort(
426 raise error.Abort(
441 _(b'working directory state appears damaged!')
427 _(b'working directory state appears damaged!')
442 )
428 )
443
429
444 return self._parents
430 return self._parents
445
431
446 def setparents(self, p1, p2):
432 def setparents(self, p1, p2):
447 self._parents = (p1, p2)
433 self._parents = (p1, p2)
448 self._dirtyparents = True
434 self._dirtyparents = True
449
435
450 def read(self):
436 def read(self):
451 # ignore HG_PENDING because identity is used only for writing
437 # ignore HG_PENDING because identity is used only for writing
452 self.identity = util.filestat.frompath(
438 self.identity = util.filestat.frompath(
453 self._opener.join(self._filename)
439 self._opener.join(self._filename)
454 )
440 )
455
441
456 try:
442 try:
457 fp = self._opendirstatefile()
443 fp = self._opendirstatefile()
458 try:
444 try:
459 st = fp.read()
445 st = fp.read()
460 finally:
446 finally:
461 fp.close()
447 fp.close()
462 except IOError as err:
448 except IOError as err:
463 if err.errno != errno.ENOENT:
449 if err.errno != errno.ENOENT:
464 raise
450 raise
465 return
451 return
466 if not st:
452 if not st:
467 return
453 return
468
454
469 if util.safehasattr(parsers, b'dict_new_presized'):
455 if util.safehasattr(parsers, b'dict_new_presized'):
470 # Make an estimate of the number of files in the dirstate based on
456 # Make an estimate of the number of files in the dirstate based on
471 # its size. This trades wasting some memory for avoiding costly
457 # its size. This trades wasting some memory for avoiding costly
472 # resizes. Each entry have a prefix of 17 bytes followed by one or
458 # resizes. Each entry have a prefix of 17 bytes followed by one or
473 # two path names. Studies on various large-scale real-world repositories
459 # two path names. Studies on various large-scale real-world repositories
474 # found 54 bytes a reasonable upper limit for the average path names.
460 # found 54 bytes a reasonable upper limit for the average path names.
475 # Copy entries are ignored for the sake of this estimate.
461 # Copy entries are ignored for the sake of this estimate.
476 self._map = parsers.dict_new_presized(len(st) // 71)
462 self._map = parsers.dict_new_presized(len(st) // 71)
477
463
478 # Python's garbage collector triggers a GC each time a certain number
464 # Python's garbage collector triggers a GC each time a certain number
479 # of container objects (the number being defined by
465 # of container objects (the number being defined by
480 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
466 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
481 # for each file in the dirstate. The C version then immediately marks
467 # for each file in the dirstate. The C version then immediately marks
482 # them as not to be tracked by the collector. However, this has no
468 # them as not to be tracked by the collector. However, this has no
483 # effect on when GCs are triggered, only on what objects the GC looks
469 # effect on when GCs are triggered, only on what objects the GC looks
484 # into. This means that O(number of files) GCs are unavoidable.
470 # into. This means that O(number of files) GCs are unavoidable.
485 # Depending on when in the process's lifetime the dirstate is parsed,
471 # Depending on when in the process's lifetime the dirstate is parsed,
486 # this can get very expensive. As a workaround, disable GC while
472 # this can get very expensive. As a workaround, disable GC while
487 # parsing the dirstate.
473 # parsing the dirstate.
488 #
474 #
489 # (we cannot decorate the function directly since it is in a C module)
475 # (we cannot decorate the function directly since it is in a C module)
490 parse_dirstate = util.nogc(parsers.parse_dirstate)
476 parse_dirstate = util.nogc(parsers.parse_dirstate)
491 p = parse_dirstate(self._map, self.copymap, st)
477 p = parse_dirstate(self._map, self.copymap, st)
492 if not self._dirtyparents:
478 if not self._dirtyparents:
493 self.setparents(*p)
479 self.setparents(*p)
494
480
495 # Avoid excess attribute lookups by fast pathing certain checks
481 # Avoid excess attribute lookups by fast pathing certain checks
496 self.__contains__ = self._map.__contains__
482 self.__contains__ = self._map.__contains__
497 self.__getitem__ = self._map.__getitem__
483 self.__getitem__ = self._map.__getitem__
498 self.get = self._map.get
484 self.get = self._map.get
499
485
500 def write(self, _tr, st, now):
486 def write(self, _tr, st, now):
501 st.write(
487 st.write(
502 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
488 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
503 )
489 )
504 st.close()
490 st.close()
505 self._dirtyparents = False
491 self._dirtyparents = False
506 self.nonnormalset, self.otherparentset = self.nonnormalentries()
492 self.nonnormalset, self.otherparentset = self.nonnormalentries()
507
493
508 @propertycache
494 @propertycache
509 def nonnormalset(self):
495 def nonnormalset(self):
510 nonnorm, otherparents = self.nonnormalentries()
496 nonnorm, otherparents = self.nonnormalentries()
511 self.otherparentset = otherparents
497 self.otherparentset = otherparents
512 return nonnorm
498 return nonnorm
513
499
514 @propertycache
500 @propertycache
515 def otherparentset(self):
501 def otherparentset(self):
516 nonnorm, otherparents = self.nonnormalentries()
502 nonnorm, otherparents = self.nonnormalentries()
517 self.nonnormalset = nonnorm
503 self.nonnormalset = nonnorm
518 return otherparents
504 return otherparents
519
505
520 def non_normal_or_other_parent_paths(self):
506 def non_normal_or_other_parent_paths(self):
521 return self.nonnormalset.union(self.otherparentset)
507 return self.nonnormalset.union(self.otherparentset)
522
508
523 @propertycache
509 @propertycache
524 def identity(self):
510 def identity(self):
525 self._map
511 self._map
526 return self.identity
512 return self.identity
527
513
528 @propertycache
514 @propertycache
529 def dirfoldmap(self):
515 def dirfoldmap(self):
530 f = {}
516 f = {}
531 normcase = util.normcase
517 normcase = util.normcase
532 for name in self._dirs:
518 for name in self._dirs:
533 f[normcase(name)] = name
519 f[normcase(name)] = name
534 return f
520 return f
535
521
536
522
537 if rustmod is not None:
523 if rustmod is not None:
538
524
539 class dirstatemap(object):
525 class dirstatemap(object):
540 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
526 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
541 self._use_dirstate_v2 = use_dirstate_v2
527 self._use_dirstate_v2 = use_dirstate_v2
542 self._nodeconstants = nodeconstants
528 self._nodeconstants = nodeconstants
543 self._ui = ui
529 self._ui = ui
544 self._opener = opener
530 self._opener = opener
545 self._root = root
531 self._root = root
546 self._filename = b'dirstate'
532 self._filename = b'dirstate'
547 self._nodelen = 20 # Also update Rust code when changing this!
533 self._nodelen = 20 # Also update Rust code when changing this!
548 self._parents = None
534 self._parents = None
549 self._dirtyparents = False
535 self._dirtyparents = False
550 self._docket = None
536 self._docket = None
551
537
552 # for consistent view between _pl() and _read() invocations
538 # for consistent view between _pl() and _read() invocations
553 self._pendingmode = None
539 self._pendingmode = None
554
540
555 self._use_dirstate_tree = self._ui.configbool(
541 self._use_dirstate_tree = self._ui.configbool(
556 b"experimental",
542 b"experimental",
557 b"dirstate-tree.in-memory",
543 b"dirstate-tree.in-memory",
558 False,
544 False,
559 )
545 )
560
546
561 def addfile(
547 def addfile(
562 self,
548 self,
563 f,
549 f,
564 mode=0,
550 mode=0,
565 size=None,
551 size=None,
566 mtime=None,
552 mtime=None,
567 added=False,
553 added=False,
568 merged=False,
554 merged=False,
569 from_p2=False,
555 from_p2=False,
570 possibly_dirty=False,
556 possibly_dirty=False,
571 ):
557 ):
572 return self._rustmap.addfile(
558 return self._rustmap.addfile(
573 f,
559 f,
574 mode,
560 mode,
575 size,
561 size,
576 mtime,
562 mtime,
577 added,
563 added,
578 merged,
564 merged,
579 from_p2,
565 from_p2,
580 possibly_dirty,
566 possibly_dirty,
581 )
567 )
582
568
583 def reset_state(
569 def reset_state(
584 self,
570 self,
585 filename,
571 filename,
586 wc_tracked,
572 wc_tracked,
587 p1_tracked,
573 p1_tracked,
588 p2_tracked=False,
574 p2_tracked=False,
589 merged=False,
575 merged=False,
590 clean_p1=False,
576 clean_p1=False,
591 clean_p2=False,
577 clean_p2=False,
592 possibly_dirty=False,
578 possibly_dirty=False,
593 parentfiledata=None,
579 parentfiledata=None,
594 ):
580 ):
595 """Set a entry to a given state, disregarding all previous state
581 """Set a entry to a given state, disregarding all previous state
596
582
597 This is to be used by the part of the dirstate API dedicated to
583 This is to be used by the part of the dirstate API dedicated to
598 adjusting the dirstate after a update/merge.
584 adjusting the dirstate after a update/merge.
599
585
600 note: calling this might result to no entry existing at all if the
586 note: calling this might result to no entry existing at all if the
601 dirstate map does not see any point at having one for this file
587 dirstate map does not see any point at having one for this file
602 anymore.
588 anymore.
603 """
589 """
604 if merged and (clean_p1 or clean_p2):
590 if merged and (clean_p1 or clean_p2):
605 msg = (
591 msg = (
606 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
592 b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
607 )
593 )
608 raise error.ProgrammingError(msg)
594 raise error.ProgrammingError(msg)
609 # copy information are now outdated
595 # copy information are now outdated
610 # (maybe new information should be in directly passed to this function)
596 # (maybe new information should be in directly passed to this function)
611 self.copymap.pop(filename, None)
597 self.copymap.pop(filename, None)
612
598
613 if not (p1_tracked or p2_tracked or wc_tracked):
599 if not (p1_tracked or p2_tracked or wc_tracked):
614 self.dropfile(filename)
600 self.dropfile(filename)
615 elif merged:
601 elif merged:
616 # XXX might be merged and removed ?
602 # XXX might be merged and removed ?
617 entry = self.get(filename)
603 entry = self.get(filename)
618 if entry is not None and entry.tracked:
604 if entry is not None and entry.tracked:
619 # XXX mostly replicate dirstate.other parent. We should get
605 # XXX mostly replicate dirstate.other parent. We should get
620 # the higher layer to pass us more reliable data where `merged`
606 # the higher layer to pass us more reliable data where `merged`
621 # actually mean merged. Dropping the else clause will show
607 # actually mean merged. Dropping the else clause will show
622 # failure in `test-graft.t`
608 # failure in `test-graft.t`
623 self.addfile(filename, merged=True)
609 self.addfile(filename, merged=True)
624 else:
610 else:
625 self.addfile(filename, from_p2=True)
611 self.addfile(filename, from_p2=True)
626 elif not (p1_tracked or p2_tracked) and wc_tracked:
612 elif not (p1_tracked or p2_tracked) and wc_tracked:
627 self.addfile(
613 self.addfile(
628 filename, added=True, possibly_dirty=possibly_dirty
614 filename, added=True, possibly_dirty=possibly_dirty
629 )
615 )
630 elif (p1_tracked or p2_tracked) and not wc_tracked:
616 elif (p1_tracked or p2_tracked) and not wc_tracked:
631 # XXX might be merged and removed ?
617 # XXX might be merged and removed ?
632 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
618 self[filename] = DirstateItem.from_v1_data(b'r', 0, 0, 0)
633 self.nonnormalset.add(filename)
619 self.nonnormalset.add(filename)
634 elif clean_p2 and wc_tracked:
620 elif clean_p2 and wc_tracked:
635 if p1_tracked or self.get(filename) is not None:
621 if p1_tracked or self.get(filename) is not None:
636 # XXX the `self.get` call is catching some case in
622 # XXX the `self.get` call is catching some case in
637 # `test-merge-remove.t` where the file is tracked in p1, the
623 # `test-merge-remove.t` where the file is tracked in p1, the
638 # p1_tracked argument is False.
624 # p1_tracked argument is False.
639 #
625 #
640 # In addition, this seems to be a case where the file is marked
626 # In addition, this seems to be a case where the file is marked
641 # as merged without actually being the result of a merge
627 # as merged without actually being the result of a merge
642 # action. So thing are not ideal here.
628 # action. So thing are not ideal here.
643 self.addfile(filename, merged=True)
629 self.addfile(filename, merged=True)
644 else:
630 else:
645 self.addfile(filename, from_p2=True)
631 self.addfile(filename, from_p2=True)
646 elif not p1_tracked and p2_tracked and wc_tracked:
632 elif not p1_tracked and p2_tracked and wc_tracked:
647 self.addfile(
633 self.addfile(
648 filename, from_p2=True, possibly_dirty=possibly_dirty
634 filename, from_p2=True, possibly_dirty=possibly_dirty
649 )
635 )
650 elif possibly_dirty:
636 elif possibly_dirty:
651 self.addfile(filename, possibly_dirty=possibly_dirty)
637 self.addfile(filename, possibly_dirty=possibly_dirty)
652 elif wc_tracked:
638 elif wc_tracked:
653 # this is a "normal" file
639 # this is a "normal" file
654 if parentfiledata is None:
640 if parentfiledata is None:
655 msg = b'failed to pass parentfiledata for a normal file: %s'
641 msg = b'failed to pass parentfiledata for a normal file: %s'
656 msg %= filename
642 msg %= filename
657 raise error.ProgrammingError(msg)
643 raise error.ProgrammingError(msg)
658 mode, size, mtime = parentfiledata
644 mode, size, mtime = parentfiledata
659 self.addfile(filename, mode=mode, size=size, mtime=mtime)
645 self.addfile(filename, mode=mode, size=size, mtime=mtime)
660 self.nonnormalset.discard(filename)
646 self.nonnormalset.discard(filename)
661 else:
647 else:
662 assert False, 'unreachable'
648 assert False, 'unreachable'
663
649
664 def set_untracked(self, f):
650 def set_untracked(self, f):
665 """Mark a file as no longer tracked in the dirstate map"""
651 """Mark a file as no longer tracked in the dirstate map"""
666 # in merge is only trigger more logic, so it "fine" to pass it.
652 # in merge is only trigger more logic, so it "fine" to pass it.
667 #
653 #
668 # the inner rust dirstate map code need to be adjusted once the API
654 # the inner rust dirstate map code need to be adjusted once the API
669 # for dirstate/dirstatemap/DirstateItem is a bit more settled
655 # for dirstate/dirstatemap/DirstateItem is a bit more settled
670 self._rustmap.removefile(f, in_merge=True)
656 self._rustmap.removefile(f, in_merge=True)
671
657
672 def removefile(self, *args, **kwargs):
658 def removefile(self, *args, **kwargs):
673 return self._rustmap.removefile(*args, **kwargs)
659 return self._rustmap.removefile(*args, **kwargs)
674
660
675 def dropfile(self, *args, **kwargs):
661 def dropfile(self, *args, **kwargs):
676 return self._rustmap.dropfile(*args, **kwargs)
662 return self._rustmap.dropfile(*args, **kwargs)
677
663
678 def clearambiguoustimes(self, *args, **kwargs):
664 def clearambiguoustimes(self, *args, **kwargs):
679 return self._rustmap.clearambiguoustimes(*args, **kwargs)
665 return self._rustmap.clearambiguoustimes(*args, **kwargs)
680
666
681 def nonnormalentries(self):
667 def nonnormalentries(self):
682 return self._rustmap.nonnormalentries()
668 return self._rustmap.nonnormalentries()
683
669
684 def get(self, *args, **kwargs):
670 def get(self, *args, **kwargs):
685 return self._rustmap.get(*args, **kwargs)
671 return self._rustmap.get(*args, **kwargs)
686
672
687 @property
673 @property
688 def copymap(self):
674 def copymap(self):
689 return self._rustmap.copymap()
675 return self._rustmap.copymap()
690
676
691 def directories(self):
677 def directories(self):
692 return self._rustmap.directories()
678 return self._rustmap.directories()
693
679
694 def debug_iter(self):
680 def debug_iter(self):
695 return self._rustmap.debug_iter()
681 return self._rustmap.debug_iter()
696
682
697 def preload(self):
683 def preload(self):
698 self._rustmap
684 self._rustmap
699
685
700 def clear(self):
686 def clear(self):
701 self._rustmap.clear()
687 self._rustmap.clear()
702 self.setparents(
688 self.setparents(
703 self._nodeconstants.nullid, self._nodeconstants.nullid
689 self._nodeconstants.nullid, self._nodeconstants.nullid
704 )
690 )
705 util.clearcachedproperty(self, b"_dirs")
691 util.clearcachedproperty(self, b"_dirs")
706 util.clearcachedproperty(self, b"_alldirs")
692 util.clearcachedproperty(self, b"_alldirs")
707 util.clearcachedproperty(self, b"dirfoldmap")
693 util.clearcachedproperty(self, b"dirfoldmap")
708
694
709 def items(self):
695 def items(self):
710 return self._rustmap.items()
696 return self._rustmap.items()
711
697
712 def keys(self):
698 def keys(self):
713 return iter(self._rustmap)
699 return iter(self._rustmap)
714
700
715 def __contains__(self, key):
701 def __contains__(self, key):
716 return key in self._rustmap
702 return key in self._rustmap
717
703
718 def __getitem__(self, item):
704 def __getitem__(self, item):
719 return self._rustmap[item]
705 return self._rustmap[item]
720
706
721 def __len__(self):
707 def __len__(self):
722 return len(self._rustmap)
708 return len(self._rustmap)
723
709
724 def __iter__(self):
710 def __iter__(self):
725 return iter(self._rustmap)
711 return iter(self._rustmap)
726
712
727 # forward for python2,3 compat
713 # forward for python2,3 compat
728 iteritems = items
714 iteritems = items
729
715
730 def _opendirstatefile(self):
716 def _opendirstatefile(self):
731 fp, mode = txnutil.trypending(
717 fp, mode = txnutil.trypending(
732 self._root, self._opener, self._filename
718 self._root, self._opener, self._filename
733 )
719 )
734 if self._pendingmode is not None and self._pendingmode != mode:
720 if self._pendingmode is not None and self._pendingmode != mode:
735 fp.close()
721 fp.close()
736 raise error.Abort(
722 raise error.Abort(
737 _(b'working directory state may be changed parallelly')
723 _(b'working directory state may be changed parallelly')
738 )
724 )
739 self._pendingmode = mode
725 self._pendingmode = mode
740 return fp
726 return fp
741
727
742 def _readdirstatefile(self, size=-1):
728 def _readdirstatefile(self, size=-1):
743 try:
729 try:
744 with self._opendirstatefile() as fp:
730 with self._opendirstatefile() as fp:
745 return fp.read(size)
731 return fp.read(size)
746 except IOError as err:
732 except IOError as err:
747 if err.errno != errno.ENOENT:
733 if err.errno != errno.ENOENT:
748 raise
734 raise
749 # File doesn't exist, so the current state is empty
735 # File doesn't exist, so the current state is empty
750 return b''
736 return b''
751
737
752 def setparents(self, p1, p2):
738 def setparents(self, p1, p2):
753 self._parents = (p1, p2)
739 self._parents = (p1, p2)
754 self._dirtyparents = True
740 self._dirtyparents = True
755
741
756 def parents(self):
742 def parents(self):
757 if not self._parents:
743 if not self._parents:
758 if self._use_dirstate_v2:
744 if self._use_dirstate_v2:
759 self._parents = self.docket.parents
745 self._parents = self.docket.parents
760 else:
746 else:
761 read_len = self._nodelen * 2
747 read_len = self._nodelen * 2
762 st = self._readdirstatefile(read_len)
748 st = self._readdirstatefile(read_len)
763 l = len(st)
749 l = len(st)
764 if l == read_len:
750 if l == read_len:
765 self._parents = (
751 self._parents = (
766 st[: self._nodelen],
752 st[: self._nodelen],
767 st[self._nodelen : 2 * self._nodelen],
753 st[self._nodelen : 2 * self._nodelen],
768 )
754 )
769 elif l == 0:
755 elif l == 0:
770 self._parents = (
756 self._parents = (
771 self._nodeconstants.nullid,
757 self._nodeconstants.nullid,
772 self._nodeconstants.nullid,
758 self._nodeconstants.nullid,
773 )
759 )
774 else:
760 else:
775 raise error.Abort(
761 raise error.Abort(
776 _(b'working directory state appears damaged!')
762 _(b'working directory state appears damaged!')
777 )
763 )
778
764
779 return self._parents
765 return self._parents
780
766
781 @property
767 @property
782 def docket(self):
768 def docket(self):
783 if not self._docket:
769 if not self._docket:
784 if not self._use_dirstate_v2:
770 if not self._use_dirstate_v2:
785 raise error.ProgrammingError(
771 raise error.ProgrammingError(
786 b'dirstate only has a docket in v2 format'
772 b'dirstate only has a docket in v2 format'
787 )
773 )
788 self._docket = docketmod.DirstateDocket.parse(
774 self._docket = docketmod.DirstateDocket.parse(
789 self._readdirstatefile(), self._nodeconstants
775 self._readdirstatefile(), self._nodeconstants
790 )
776 )
791 return self._docket
777 return self._docket
792
778
793 @propertycache
779 @propertycache
794 def _rustmap(self):
780 def _rustmap(self):
795 """
781 """
796 Fills the Dirstatemap when called.
782 Fills the Dirstatemap when called.
797 """
783 """
798 # ignore HG_PENDING because identity is used only for writing
784 # ignore HG_PENDING because identity is used only for writing
799 self.identity = util.filestat.frompath(
785 self.identity = util.filestat.frompath(
800 self._opener.join(self._filename)
786 self._opener.join(self._filename)
801 )
787 )
802
788
803 if self._use_dirstate_v2:
789 if self._use_dirstate_v2:
804 if self.docket.uuid:
790 if self.docket.uuid:
805 # TODO: use mmap when possible
791 # TODO: use mmap when possible
806 data = self._opener.read(self.docket.data_filename())
792 data = self._opener.read(self.docket.data_filename())
807 else:
793 else:
808 data = b''
794 data = b''
809 self._rustmap = rustmod.DirstateMap.new_v2(
795 self._rustmap = rustmod.DirstateMap.new_v2(
810 data, self.docket.data_size, self.docket.tree_metadata
796 data, self.docket.data_size, self.docket.tree_metadata
811 )
797 )
812 parents = self.docket.parents
798 parents = self.docket.parents
813 else:
799 else:
814 self._rustmap, parents = rustmod.DirstateMap.new_v1(
800 self._rustmap, parents = rustmod.DirstateMap.new_v1(
815 self._use_dirstate_tree, self._readdirstatefile()
801 self._use_dirstate_tree, self._readdirstatefile()
816 )
802 )
817
803
818 if parents and not self._dirtyparents:
804 if parents and not self._dirtyparents:
819 self.setparents(*parents)
805 self.setparents(*parents)
820
806
821 self.__contains__ = self._rustmap.__contains__
807 self.__contains__ = self._rustmap.__contains__
822 self.__getitem__ = self._rustmap.__getitem__
808 self.__getitem__ = self._rustmap.__getitem__
823 self.get = self._rustmap.get
809 self.get = self._rustmap.get
824 return self._rustmap
810 return self._rustmap
825
811
826 def write(self, tr, st, now):
812 def write(self, tr, st, now):
827 if not self._use_dirstate_v2:
813 if not self._use_dirstate_v2:
828 p1, p2 = self.parents()
814 p1, p2 = self.parents()
829 packed = self._rustmap.write_v1(p1, p2, now)
815 packed = self._rustmap.write_v1(p1, p2, now)
830 st.write(packed)
816 st.write(packed)
831 st.close()
817 st.close()
832 self._dirtyparents = False
818 self._dirtyparents = False
833 return
819 return
834
820
835 # We can only append to an existing data file if there is one
821 # We can only append to an existing data file if there is one
836 can_append = self.docket.uuid is not None
822 can_append = self.docket.uuid is not None
837 packed, meta, append = self._rustmap.write_v2(now, can_append)
823 packed, meta, append = self._rustmap.write_v2(now, can_append)
838 if append:
824 if append:
839 docket = self.docket
825 docket = self.docket
840 data_filename = docket.data_filename()
826 data_filename = docket.data_filename()
841 if tr:
827 if tr:
842 tr.add(data_filename, docket.data_size)
828 tr.add(data_filename, docket.data_size)
843 with self._opener(data_filename, b'r+b') as fp:
829 with self._opener(data_filename, b'r+b') as fp:
844 fp.seek(docket.data_size)
830 fp.seek(docket.data_size)
845 assert fp.tell() == docket.data_size
831 assert fp.tell() == docket.data_size
846 written = fp.write(packed)
832 written = fp.write(packed)
847 if written is not None: # py2 may return None
833 if written is not None: # py2 may return None
848 assert written == len(packed), (written, len(packed))
834 assert written == len(packed), (written, len(packed))
849 docket.data_size += len(packed)
835 docket.data_size += len(packed)
850 docket.parents = self.parents()
836 docket.parents = self.parents()
851 docket.tree_metadata = meta
837 docket.tree_metadata = meta
852 st.write(docket.serialize())
838 st.write(docket.serialize())
853 st.close()
839 st.close()
854 else:
840 else:
855 old_docket = self.docket
841 old_docket = self.docket
856 new_docket = docketmod.DirstateDocket.with_new_uuid(
842 new_docket = docketmod.DirstateDocket.with_new_uuid(
857 self.parents(), len(packed), meta
843 self.parents(), len(packed), meta
858 )
844 )
859 data_filename = new_docket.data_filename()
845 data_filename = new_docket.data_filename()
860 if tr:
846 if tr:
861 tr.add(data_filename, 0)
847 tr.add(data_filename, 0)
862 self._opener.write(data_filename, packed)
848 self._opener.write(data_filename, packed)
863 # Write the new docket after the new data file has been
849 # Write the new docket after the new data file has been
864 # written. Because `st` was opened with `atomictemp=True`,
850 # written. Because `st` was opened with `atomictemp=True`,
865 # the actual `.hg/dirstate` file is only affected on close.
851 # the actual `.hg/dirstate` file is only affected on close.
866 st.write(new_docket.serialize())
852 st.write(new_docket.serialize())
867 st.close()
853 st.close()
868 # Remove the old data file after the new docket pointing to
854 # Remove the old data file after the new docket pointing to
869 # the new data file was written.
855 # the new data file was written.
870 if old_docket.uuid:
856 if old_docket.uuid:
871 data_filename = old_docket.data_filename()
857 data_filename = old_docket.data_filename()
872 unlink = lambda _tr=None: self._opener.unlink(data_filename)
858 unlink = lambda _tr=None: self._opener.unlink(data_filename)
873 if tr:
859 if tr:
874 category = b"dirstate-v2-clean-" + old_docket.uuid
860 category = b"dirstate-v2-clean-" + old_docket.uuid
875 tr.addpostclose(category, unlink)
861 tr.addpostclose(category, unlink)
876 else:
862 else:
877 unlink()
863 unlink()
878 self._docket = new_docket
864 self._docket = new_docket
879 # Reload from the newly-written file
865 # Reload from the newly-written file
880 util.clearcachedproperty(self, b"_rustmap")
866 util.clearcachedproperty(self, b"_rustmap")
881 self._dirtyparents = False
867 self._dirtyparents = False
882
868
883 @propertycache
869 @propertycache
884 def filefoldmap(self):
870 def filefoldmap(self):
885 """Returns a dictionary mapping normalized case paths to their
871 """Returns a dictionary mapping normalized case paths to their
886 non-normalized versions.
872 non-normalized versions.
887 """
873 """
888 return self._rustmap.filefoldmapasdict()
874 return self._rustmap.filefoldmapasdict()
889
875
890 def hastrackeddir(self, d):
876 def hastrackeddir(self, d):
891 return self._rustmap.hastrackeddir(d)
877 return self._rustmap.hastrackeddir(d)
892
878
893 def hasdir(self, d):
879 def hasdir(self, d):
894 return self._rustmap.hasdir(d)
880 return self._rustmap.hasdir(d)
895
881
896 @propertycache
882 @propertycache
897 def identity(self):
883 def identity(self):
898 self._rustmap
884 self._rustmap
899 return self.identity
885 return self.identity
900
886
901 @property
887 @property
902 def nonnormalset(self):
888 def nonnormalset(self):
903 nonnorm = self._rustmap.non_normal_entries()
889 nonnorm = self._rustmap.non_normal_entries()
904 return nonnorm
890 return nonnorm
905
891
906 @propertycache
892 @propertycache
907 def otherparentset(self):
893 def otherparentset(self):
908 otherparents = self._rustmap.other_parent_entries()
894 otherparents = self._rustmap.other_parent_entries()
909 return otherparents
895 return otherparents
910
896
911 def non_normal_or_other_parent_paths(self):
897 def non_normal_or_other_parent_paths(self):
912 return self._rustmap.non_normal_or_other_parent_paths()
898 return self._rustmap.non_normal_or_other_parent_paths()
913
899
914 @propertycache
900 @propertycache
915 def dirfoldmap(self):
901 def dirfoldmap(self):
916 f = {}
902 f = {}
917 normcase = util.normcase
903 normcase = util.normcase
918 for name in self._rustmap.tracked_dirs():
904 for name in self._rustmap.tracked_dirs():
919 f[normcase(name)] = name
905 f[normcase(name)] = name
920 return f
906 return f
921
907
922 def set_possibly_dirty(self, filename):
908 def set_possibly_dirty(self, filename):
923 """record that the current state of the file on disk is unknown"""
909 """record that the current state of the file on disk is unknown"""
924 entry = self[filename]
910 entry = self[filename]
925 entry.set_possibly_dirty()
911 entry.set_possibly_dirty()
926 self._rustmap.set_v1(filename, entry)
912 self._rustmap.set_v1(filename, entry)
927
913
928 def __setitem__(self, key, value):
914 def __setitem__(self, key, value):
929 assert isinstance(value, DirstateItem)
915 assert isinstance(value, DirstateItem)
930 self._rustmap.set_v1(key, value)
916 self._rustmap.set_v1(key, value)
@@ -1,688 +1,755 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 _state = attr.ib()
59 _state = attr.ib()
60 _mode = attr.ib()
60 _mode = attr.ib()
61 _size = attr.ib()
61 _size = attr.ib()
62 _mtime = attr.ib()
62 _mtime = attr.ib()
63
63
64 def __init__(
64 def __init__(
65 self,
65 self,
66 wc_tracked=False,
66 wc_tracked=False,
67 p1_tracked=False,
67 p1_tracked=False,
68 p2_tracked=False,
68 p2_tracked=False,
69 merged=False,
69 merged=False,
70 clean_p1=False,
70 clean_p1=False,
71 clean_p2=False,
71 clean_p2=False,
72 possibly_dirty=False,
72 possibly_dirty=False,
73 parentfiledata=None,
73 parentfiledata=None,
74 ):
74 ):
75 if merged and (clean_p1 or clean_p2):
75 if merged and (clean_p1 or clean_p2):
76 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
76 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
77 raise error.ProgrammingError(msg)
77 raise error.ProgrammingError(msg)
78
78
79 self._state = None
79 self._state = None
80 self._mode = 0
80 self._mode = 0
81 self._size = NONNORMAL
81 self._size = NONNORMAL
82 self._mtime = AMBIGUOUS_TIME
82 self._mtime = AMBIGUOUS_TIME
83 if not (p1_tracked or p2_tracked or wc_tracked):
83 if not (p1_tracked or p2_tracked or wc_tracked):
84 pass # the object has no state to record
84 pass # the object has no state to record
85 elif merged:
85 elif merged:
86 self._state = b'm'
86 self._state = b'm'
87 self._size = FROM_P2
87 self._size = FROM_P2
88 self._mtime = AMBIGUOUS_TIME
88 self._mtime = AMBIGUOUS_TIME
89 elif not (p1_tracked or p2_tracked) and wc_tracked:
89 elif not (p1_tracked or p2_tracked) and wc_tracked:
90 self._state = b'a'
90 self._state = b'a'
91 self._size = NONNORMAL
91 self._size = NONNORMAL
92 self._mtime = AMBIGUOUS_TIME
92 self._mtime = AMBIGUOUS_TIME
93 elif (p1_tracked or p2_tracked) and not wc_tracked:
93 elif (p1_tracked or p2_tracked) and not wc_tracked:
94 self._state = b'r'
94 self._state = b'r'
95 self._size = 0
95 self._size = 0
96 self._mtime = 0
96 self._mtime = 0
97 elif clean_p2 and wc_tracked:
97 elif clean_p2 and wc_tracked:
98 self._state = b'n'
98 self._state = b'n'
99 self._size = FROM_P2
99 self._size = FROM_P2
100 self._mtime = AMBIGUOUS_TIME
100 self._mtime = AMBIGUOUS_TIME
101 elif not p1_tracked and p2_tracked and wc_tracked:
101 elif not p1_tracked and p2_tracked and wc_tracked:
102 self._state = b'n'
102 self._state = b'n'
103 self._size = FROM_P2
103 self._size = FROM_P2
104 self._mtime = AMBIGUOUS_TIME
104 self._mtime = AMBIGUOUS_TIME
105 elif possibly_dirty:
105 elif possibly_dirty:
106 self._state = b'n'
106 self._state = b'n'
107 self._size = NONNORMAL
107 self._size = NONNORMAL
108 self._mtime = AMBIGUOUS_TIME
108 self._mtime = AMBIGUOUS_TIME
109 elif wc_tracked:
109 elif wc_tracked:
110 # this is a "normal" file
110 # this is a "normal" file
111 if parentfiledata is None:
111 if parentfiledata is None:
112 msg = b'failed to pass parentfiledata for a normal file'
112 msg = b'failed to pass parentfiledata for a normal file'
113 raise error.ProgrammingError(msg)
113 raise error.ProgrammingError(msg)
114 self._state = b'n'
114 self._state = b'n'
115 self._mode = parentfiledata[0]
115 self._mode = parentfiledata[0]
116 self._size = parentfiledata[1]
116 self._size = parentfiledata[1]
117 self._mtime = parentfiledata[2]
117 self._mtime = parentfiledata[2]
118 else:
118 else:
119 assert False, 'unreachable'
119 assert False, 'unreachable'
120
120
121 @classmethod
121 @classmethod
122 def new_added(cls):
123 """constructor to help legacy API to build a new "added" item
124
125 Should eventually be removed
126 """
127 instance = cls()
128 instance._state = b'a'
129 instance._mode = 0
130 instance._size = NONNORMAL
131 instance._mtime = AMBIGUOUS_TIME
132 return instance
133
134 @classmethod
135 def new_merged(cls):
136 """constructor to help legacy API to build a new "merged" item
137
138 Should eventually be removed
139 """
140 instance = cls()
141 instance._state = b'm'
142 instance._mode = 0
143 instance._size = FROM_P2
144 instance._mtime = AMBIGUOUS_TIME
145 return instance
146
147 @classmethod
148 def new_from_p2(cls):
149 """constructor to help legacy API to build a new "from_p2" item
150
151 Should eventually be removed
152 """
153 instance = cls()
154 instance._state = b'n'
155 instance._mode = 0
156 instance._size = FROM_P2
157 instance._mtime = AMBIGUOUS_TIME
158 return instance
159
160 @classmethod
161 def new_possibly_dirty(cls):
162 """constructor to help legacy API to build a new "possibly_dirty" item
163
164 Should eventually be removed
165 """
166 instance = cls()
167 instance._state = b'n'
168 instance._mode = 0
169 instance._size = NONNORMAL
170 instance._mtime = AMBIGUOUS_TIME
171 return instance
172
173 @classmethod
174 def new_normal(cls, mode, size, mtime):
175 """constructor to help legacy API to build a new "normal" item
176
177 Should eventually be removed
178 """
179 assert size != FROM_P2
180 assert size != NONNORMAL
181 instance = cls()
182 instance._state = b'n'
183 instance._mode = mode
184 instance._size = size
185 instance._mtime = mtime
186 return instance
187
188 @classmethod
122 def from_v1_data(cls, state, mode, size, mtime):
189 def from_v1_data(cls, state, mode, size, mtime):
123 """Build a new DirstateItem object from V1 data
190 """Build a new DirstateItem object from V1 data
124
191
125 Since the dirstate-v1 format is frozen, the signature of this function
192 Since the dirstate-v1 format is frozen, the signature of this function
126 is not expected to change, unlike the __init__ one.
193 is not expected to change, unlike the __init__ one.
127 """
194 """
128 instance = cls()
195 instance = cls()
129 instance._state = state
196 instance._state = state
130 instance._mode = mode
197 instance._mode = mode
131 instance._size = size
198 instance._size = size
132 instance._mtime = mtime
199 instance._mtime = mtime
133 return instance
200 return instance
134
201
135 def set_possibly_dirty(self):
202 def set_possibly_dirty(self):
136 """Mark a file as "possibly dirty"
203 """Mark a file as "possibly dirty"
137
204
138 This means the next status call will have to actually check its content
205 This means the next status call will have to actually check its content
139 to make sure it is correct.
206 to make sure it is correct.
140 """
207 """
141 self._mtime = AMBIGUOUS_TIME
208 self._mtime = AMBIGUOUS_TIME
142
209
143 def set_untracked(self):
210 def set_untracked(self):
144 """mark a file as untracked in the working copy
211 """mark a file as untracked in the working copy
145
212
146 This will ultimately be called by command like `hg remove`.
213 This will ultimately be called by command like `hg remove`.
147 """
214 """
148 # backup the previous state (useful for merge)
215 # backup the previous state (useful for merge)
149 size = 0
216 size = 0
150 if self.merged: # merge
217 if self.merged: # merge
151 size = NONNORMAL
218 size = NONNORMAL
152 elif self.from_p2:
219 elif self.from_p2:
153 size = FROM_P2
220 size = FROM_P2
154 self._state = b'r'
221 self._state = b'r'
155 self._mode = 0
222 self._mode = 0
156 self._size = size
223 self._size = size
157 self._mtime = 0
224 self._mtime = 0
158
225
159 def __getitem__(self, idx):
226 def __getitem__(self, idx):
160 if idx == 0 or idx == -4:
227 if idx == 0 or idx == -4:
161 msg = b"do not use item[x], use item.state"
228 msg = b"do not use item[x], use item.state"
162 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
229 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
163 return self._state
230 return self._state
164 elif idx == 1 or idx == -3:
231 elif idx == 1 or idx == -3:
165 msg = b"do not use item[x], use item.mode"
232 msg = b"do not use item[x], use item.mode"
166 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
233 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
167 return self._mode
234 return self._mode
168 elif idx == 2 or idx == -2:
235 elif idx == 2 or idx == -2:
169 msg = b"do not use item[x], use item.size"
236 msg = b"do not use item[x], use item.size"
170 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
237 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
171 return self._size
238 return self._size
172 elif idx == 3 or idx == -1:
239 elif idx == 3 or idx == -1:
173 msg = b"do not use item[x], use item.mtime"
240 msg = b"do not use item[x], use item.mtime"
174 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
241 util.nouideprecwarn(msg, b'6.0', stacklevel=2)
175 return self._mtime
242 return self._mtime
176 else:
243 else:
177 raise IndexError(idx)
244 raise IndexError(idx)
178
245
179 @property
246 @property
180 def mode(self):
247 def mode(self):
181 return self._mode
248 return self._mode
182
249
183 @property
250 @property
184 def size(self):
251 def size(self):
185 return self._size
252 return self._size
186
253
187 @property
254 @property
188 def mtime(self):
255 def mtime(self):
189 return self._mtime
256 return self._mtime
190
257
191 @property
258 @property
192 def state(self):
259 def state(self):
193 """
260 """
194 States are:
261 States are:
195 n normal
262 n normal
196 m needs merging
263 m needs merging
197 r marked for removal
264 r marked for removal
198 a marked for addition
265 a marked for addition
199
266
200 XXX This "state" is a bit obscure and mostly a direct expression of the
267 XXX This "state" is a bit obscure and mostly a direct expression of the
201 dirstatev1 format. It would make sense to ultimately deprecate it in
268 dirstatev1 format. It would make sense to ultimately deprecate it in
202 favor of the more "semantic" attributes.
269 favor of the more "semantic" attributes.
203 """
270 """
204 return self._state
271 return self._state
205
272
206 @property
273 @property
207 def tracked(self):
274 def tracked(self):
208 """True is the file is tracked in the working copy"""
275 """True is the file is tracked in the working copy"""
209 return self._state in b"nma"
276 return self._state in b"nma"
210
277
211 @property
278 @property
212 def added(self):
279 def added(self):
213 """True if the file has been added"""
280 """True if the file has been added"""
214 return self._state == b'a'
281 return self._state == b'a'
215
282
216 @property
283 @property
217 def merged(self):
284 def merged(self):
218 """True if the file has been merged
285 """True if the file has been merged
219
286
220 Should only be set if a merge is in progress in the dirstate
287 Should only be set if a merge is in progress in the dirstate
221 """
288 """
222 return self._state == b'm'
289 return self._state == b'm'
223
290
224 @property
291 @property
225 def from_p2(self):
292 def from_p2(self):
226 """True if the file have been fetched from p2 during the current merge
293 """True if the file have been fetched from p2 during the current merge
227
294
228 This is only True is the file is currently tracked.
295 This is only True is the file is currently tracked.
229
296
230 Should only be set if a merge is in progress in the dirstate
297 Should only be set if a merge is in progress in the dirstate
231 """
298 """
232 return self._state == b'n' and self._size == FROM_P2
299 return self._state == b'n' and self._size == FROM_P2
233
300
234 @property
301 @property
235 def from_p2_removed(self):
302 def from_p2_removed(self):
236 """True if the file has been removed, but was "from_p2" initially
303 """True if the file has been removed, but was "from_p2" initially
237
304
238 This property seems like an abstraction leakage and should probably be
305 This property seems like an abstraction leakage and should probably be
239 dealt in this class (or maybe the dirstatemap) directly.
306 dealt in this class (or maybe the dirstatemap) directly.
240 """
307 """
241 return self._state == b'r' and self._size == FROM_P2
308 return self._state == b'r' and self._size == FROM_P2
242
309
243 @property
310 @property
244 def removed(self):
311 def removed(self):
245 """True if the file has been removed"""
312 """True if the file has been removed"""
246 return self._state == b'r'
313 return self._state == b'r'
247
314
248 @property
315 @property
249 def merged_removed(self):
316 def merged_removed(self):
250 """True if the file has been removed, but was "merged" initially
317 """True if the file has been removed, but was "merged" initially
251
318
252 This property seems like an abstraction leakage and should probably be
319 This property seems like an abstraction leakage and should probably be
253 dealt in this class (or maybe the dirstatemap) directly.
320 dealt in this class (or maybe the dirstatemap) directly.
254 """
321 """
255 return self._state == b'r' and self._size == NONNORMAL
322 return self._state == b'r' and self._size == NONNORMAL
256
323
257 @property
324 @property
258 def dm_nonnormal(self):
325 def dm_nonnormal(self):
259 """True is the entry is non-normal in the dirstatemap sense
326 """True is the entry is non-normal in the dirstatemap sense
260
327
261 There is no reason for any code, but the dirstatemap one to use this.
328 There is no reason for any code, but the dirstatemap one to use this.
262 """
329 """
263 return self.state != b'n' or self.mtime == AMBIGUOUS_TIME
330 return self.state != b'n' or self.mtime == AMBIGUOUS_TIME
264
331
265 @property
332 @property
266 def dm_otherparent(self):
333 def dm_otherparent(self):
267 """True is the entry is `otherparent` in the dirstatemap sense
334 """True is the entry is `otherparent` in the dirstatemap sense
268
335
269 There is no reason for any code, but the dirstatemap one to use this.
336 There is no reason for any code, but the dirstatemap one to use this.
270 """
337 """
271 return self._size == FROM_P2
338 return self._size == FROM_P2
272
339
273 def v1_state(self):
340 def v1_state(self):
274 """return a "state" suitable for v1 serialization"""
341 """return a "state" suitable for v1 serialization"""
275 return self._state
342 return self._state
276
343
277 def v1_mode(self):
344 def v1_mode(self):
278 """return a "mode" suitable for v1 serialization"""
345 """return a "mode" suitable for v1 serialization"""
279 return self._mode
346 return self._mode
280
347
281 def v1_size(self):
348 def v1_size(self):
282 """return a "size" suitable for v1 serialization"""
349 """return a "size" suitable for v1 serialization"""
283 return self._size
350 return self._size
284
351
285 def v1_mtime(self):
352 def v1_mtime(self):
286 """return a "mtime" suitable for v1 serialization"""
353 """return a "mtime" suitable for v1 serialization"""
287 return self._mtime
354 return self._mtime
288
355
289 def need_delay(self, now):
356 def need_delay(self, now):
290 """True if the stored mtime would be ambiguous with the current time"""
357 """True if the stored mtime would be ambiguous with the current time"""
291 return self._state == b'n' and self._mtime == now
358 return self._state == b'n' and self._mtime == now
292
359
293
360
294 def gettype(q):
361 def gettype(q):
295 return int(q & 0xFFFF)
362 return int(q & 0xFFFF)
296
363
297
364
298 class BaseIndexObject(object):
365 class BaseIndexObject(object):
299 # Can I be passed to an algorithme implemented in Rust ?
366 # Can I be passed to an algorithme implemented in Rust ?
300 rust_ext_compat = 0
367 rust_ext_compat = 0
301 # Format of an index entry according to Python's `struct` language
368 # Format of an index entry according to Python's `struct` language
302 index_format = revlog_constants.INDEX_ENTRY_V1
369 index_format = revlog_constants.INDEX_ENTRY_V1
303 # Size of a C unsigned long long int, platform independent
370 # Size of a C unsigned long long int, platform independent
304 big_int_size = struct.calcsize(b'>Q')
371 big_int_size = struct.calcsize(b'>Q')
305 # Size of a C long int, platform independent
372 # Size of a C long int, platform independent
306 int_size = struct.calcsize(b'>i')
373 int_size = struct.calcsize(b'>i')
307 # An empty index entry, used as a default value to be overridden, or nullrev
374 # An empty index entry, used as a default value to be overridden, or nullrev
308 null_item = (
375 null_item = (
309 0,
376 0,
310 0,
377 0,
311 0,
378 0,
312 -1,
379 -1,
313 -1,
380 -1,
314 -1,
381 -1,
315 -1,
382 -1,
316 sha1nodeconstants.nullid,
383 sha1nodeconstants.nullid,
317 0,
384 0,
318 0,
385 0,
319 revlog_constants.COMP_MODE_INLINE,
386 revlog_constants.COMP_MODE_INLINE,
320 revlog_constants.COMP_MODE_INLINE,
387 revlog_constants.COMP_MODE_INLINE,
321 )
388 )
322
389
323 @util.propertycache
390 @util.propertycache
324 def entry_size(self):
391 def entry_size(self):
325 return self.index_format.size
392 return self.index_format.size
326
393
327 @property
394 @property
328 def nodemap(self):
395 def nodemap(self):
329 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
396 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
330 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
397 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
331 return self._nodemap
398 return self._nodemap
332
399
333 @util.propertycache
400 @util.propertycache
334 def _nodemap(self):
401 def _nodemap(self):
335 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
402 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
336 for r in range(0, len(self)):
403 for r in range(0, len(self)):
337 n = self[r][7]
404 n = self[r][7]
338 nodemap[n] = r
405 nodemap[n] = r
339 return nodemap
406 return nodemap
340
407
341 def has_node(self, node):
408 def has_node(self, node):
342 """return True if the node exist in the index"""
409 """return True if the node exist in the index"""
343 return node in self._nodemap
410 return node in self._nodemap
344
411
345 def rev(self, node):
412 def rev(self, node):
346 """return a revision for a node
413 """return a revision for a node
347
414
348 If the node is unknown, raise a RevlogError"""
415 If the node is unknown, raise a RevlogError"""
349 return self._nodemap[node]
416 return self._nodemap[node]
350
417
351 def get_rev(self, node):
418 def get_rev(self, node):
352 """return a revision for a node
419 """return a revision for a node
353
420
354 If the node is unknown, return None"""
421 If the node is unknown, return None"""
355 return self._nodemap.get(node)
422 return self._nodemap.get(node)
356
423
357 def _stripnodes(self, start):
424 def _stripnodes(self, start):
358 if '_nodemap' in vars(self):
425 if '_nodemap' in vars(self):
359 for r in range(start, len(self)):
426 for r in range(start, len(self)):
360 n = self[r][7]
427 n = self[r][7]
361 del self._nodemap[n]
428 del self._nodemap[n]
362
429
363 def clearcaches(self):
430 def clearcaches(self):
364 self.__dict__.pop('_nodemap', None)
431 self.__dict__.pop('_nodemap', None)
365
432
366 def __len__(self):
433 def __len__(self):
367 return self._lgt + len(self._extra)
434 return self._lgt + len(self._extra)
368
435
369 def append(self, tup):
436 def append(self, tup):
370 if '_nodemap' in vars(self):
437 if '_nodemap' in vars(self):
371 self._nodemap[tup[7]] = len(self)
438 self._nodemap[tup[7]] = len(self)
372 data = self._pack_entry(len(self), tup)
439 data = self._pack_entry(len(self), tup)
373 self._extra.append(data)
440 self._extra.append(data)
374
441
375 def _pack_entry(self, rev, entry):
442 def _pack_entry(self, rev, entry):
376 assert entry[8] == 0
443 assert entry[8] == 0
377 assert entry[9] == 0
444 assert entry[9] == 0
378 return self.index_format.pack(*entry[:8])
445 return self.index_format.pack(*entry[:8])
379
446
380 def _check_index(self, i):
447 def _check_index(self, i):
381 if not isinstance(i, int):
448 if not isinstance(i, int):
382 raise TypeError(b"expecting int indexes")
449 raise TypeError(b"expecting int indexes")
383 if i < 0 or i >= len(self):
450 if i < 0 or i >= len(self):
384 raise IndexError
451 raise IndexError
385
452
386 def __getitem__(self, i):
453 def __getitem__(self, i):
387 if i == -1:
454 if i == -1:
388 return self.null_item
455 return self.null_item
389 self._check_index(i)
456 self._check_index(i)
390 if i >= self._lgt:
457 if i >= self._lgt:
391 data = self._extra[i - self._lgt]
458 data = self._extra[i - self._lgt]
392 else:
459 else:
393 index = self._calculate_index(i)
460 index = self._calculate_index(i)
394 data = self._data[index : index + self.entry_size]
461 data = self._data[index : index + self.entry_size]
395 r = self._unpack_entry(i, data)
462 r = self._unpack_entry(i, data)
396 if self._lgt and i == 0:
463 if self._lgt and i == 0:
397 offset = revlogutils.offset_type(0, gettype(r[0]))
464 offset = revlogutils.offset_type(0, gettype(r[0]))
398 r = (offset,) + r[1:]
465 r = (offset,) + r[1:]
399 return r
466 return r
400
467
401 def _unpack_entry(self, rev, data):
468 def _unpack_entry(self, rev, data):
402 r = self.index_format.unpack(data)
469 r = self.index_format.unpack(data)
403 r = r + (
470 r = r + (
404 0,
471 0,
405 0,
472 0,
406 revlog_constants.COMP_MODE_INLINE,
473 revlog_constants.COMP_MODE_INLINE,
407 revlog_constants.COMP_MODE_INLINE,
474 revlog_constants.COMP_MODE_INLINE,
408 )
475 )
409 return r
476 return r
410
477
411 def pack_header(self, header):
478 def pack_header(self, header):
412 """pack header information as binary"""
479 """pack header information as binary"""
413 v_fmt = revlog_constants.INDEX_HEADER
480 v_fmt = revlog_constants.INDEX_HEADER
414 return v_fmt.pack(header)
481 return v_fmt.pack(header)
415
482
416 def entry_binary(self, rev):
483 def entry_binary(self, rev):
417 """return the raw binary string representing a revision"""
484 """return the raw binary string representing a revision"""
418 entry = self[rev]
485 entry = self[rev]
419 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
486 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
420 if rev == 0:
487 if rev == 0:
421 p = p[revlog_constants.INDEX_HEADER.size :]
488 p = p[revlog_constants.INDEX_HEADER.size :]
422 return p
489 return p
423
490
424
491
425 class IndexObject(BaseIndexObject):
492 class IndexObject(BaseIndexObject):
426 def __init__(self, data):
493 def __init__(self, data):
427 assert len(data) % self.entry_size == 0, (
494 assert len(data) % self.entry_size == 0, (
428 len(data),
495 len(data),
429 self.entry_size,
496 self.entry_size,
430 len(data) % self.entry_size,
497 len(data) % self.entry_size,
431 )
498 )
432 self._data = data
499 self._data = data
433 self._lgt = len(data) // self.entry_size
500 self._lgt = len(data) // self.entry_size
434 self._extra = []
501 self._extra = []
435
502
436 def _calculate_index(self, i):
503 def _calculate_index(self, i):
437 return i * self.entry_size
504 return i * self.entry_size
438
505
439 def __delitem__(self, i):
506 def __delitem__(self, i):
440 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
507 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
441 raise ValueError(b"deleting slices only supports a:-1 with step 1")
508 raise ValueError(b"deleting slices only supports a:-1 with step 1")
442 i = i.start
509 i = i.start
443 self._check_index(i)
510 self._check_index(i)
444 self._stripnodes(i)
511 self._stripnodes(i)
445 if i < self._lgt:
512 if i < self._lgt:
446 self._data = self._data[: i * self.entry_size]
513 self._data = self._data[: i * self.entry_size]
447 self._lgt = i
514 self._lgt = i
448 self._extra = []
515 self._extra = []
449 else:
516 else:
450 self._extra = self._extra[: i - self._lgt]
517 self._extra = self._extra[: i - self._lgt]
451
518
452
519
453 class PersistentNodeMapIndexObject(IndexObject):
520 class PersistentNodeMapIndexObject(IndexObject):
454 """a Debug oriented class to test persistent nodemap
521 """a Debug oriented class to test persistent nodemap
455
522
456 We need a simple python object to test API and higher level behavior. See
523 We need a simple python object to test API and higher level behavior. See
457 the Rust implementation for more serious usage. This should be used only
524 the Rust implementation for more serious usage. This should be used only
458 through the dedicated `devel.persistent-nodemap` config.
525 through the dedicated `devel.persistent-nodemap` config.
459 """
526 """
460
527
461 def nodemap_data_all(self):
528 def nodemap_data_all(self):
462 """Return bytes containing a full serialization of a nodemap
529 """Return bytes containing a full serialization of a nodemap
463
530
464 The nodemap should be valid for the full set of revisions in the
531 The nodemap should be valid for the full set of revisions in the
465 index."""
532 index."""
466 return nodemaputil.persistent_data(self)
533 return nodemaputil.persistent_data(self)
467
534
468 def nodemap_data_incremental(self):
535 def nodemap_data_incremental(self):
469 """Return bytes containing a incremental update to persistent nodemap
536 """Return bytes containing a incremental update to persistent nodemap
470
537
471 This containst the data for an append-only update of the data provided
538 This containst the data for an append-only update of the data provided
472 in the last call to `update_nodemap_data`.
539 in the last call to `update_nodemap_data`.
473 """
540 """
474 if self._nm_root is None:
541 if self._nm_root is None:
475 return None
542 return None
476 docket = self._nm_docket
543 docket = self._nm_docket
477 changed, data = nodemaputil.update_persistent_data(
544 changed, data = nodemaputil.update_persistent_data(
478 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
545 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
479 )
546 )
480
547
481 self._nm_root = self._nm_max_idx = self._nm_docket = None
548 self._nm_root = self._nm_max_idx = self._nm_docket = None
482 return docket, changed, data
549 return docket, changed, data
483
550
484 def update_nodemap_data(self, docket, nm_data):
551 def update_nodemap_data(self, docket, nm_data):
485 """provide full block of persisted binary data for a nodemap
552 """provide full block of persisted binary data for a nodemap
486
553
487 The data are expected to come from disk. See `nodemap_data_all` for a
554 The data are expected to come from disk. See `nodemap_data_all` for a
488 produceur of such data."""
555 produceur of such data."""
489 if nm_data is not None:
556 if nm_data is not None:
490 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
557 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
491 if self._nm_root:
558 if self._nm_root:
492 self._nm_docket = docket
559 self._nm_docket = docket
493 else:
560 else:
494 self._nm_root = self._nm_max_idx = self._nm_docket = None
561 self._nm_root = self._nm_max_idx = self._nm_docket = None
495
562
496
563
497 class InlinedIndexObject(BaseIndexObject):
564 class InlinedIndexObject(BaseIndexObject):
498 def __init__(self, data, inline=0):
565 def __init__(self, data, inline=0):
499 self._data = data
566 self._data = data
500 self._lgt = self._inline_scan(None)
567 self._lgt = self._inline_scan(None)
501 self._inline_scan(self._lgt)
568 self._inline_scan(self._lgt)
502 self._extra = []
569 self._extra = []
503
570
504 def _inline_scan(self, lgt):
571 def _inline_scan(self, lgt):
505 off = 0
572 off = 0
506 if lgt is not None:
573 if lgt is not None:
507 self._offsets = [0] * lgt
574 self._offsets = [0] * lgt
508 count = 0
575 count = 0
509 while off <= len(self._data) - self.entry_size:
576 while off <= len(self._data) - self.entry_size:
510 start = off + self.big_int_size
577 start = off + self.big_int_size
511 (s,) = struct.unpack(
578 (s,) = struct.unpack(
512 b'>i',
579 b'>i',
513 self._data[start : start + self.int_size],
580 self._data[start : start + self.int_size],
514 )
581 )
515 if lgt is not None:
582 if lgt is not None:
516 self._offsets[count] = off
583 self._offsets[count] = off
517 count += 1
584 count += 1
518 off += self.entry_size + s
585 off += self.entry_size + s
519 if off != len(self._data):
586 if off != len(self._data):
520 raise ValueError(b"corrupted data")
587 raise ValueError(b"corrupted data")
521 return count
588 return count
522
589
523 def __delitem__(self, i):
590 def __delitem__(self, i):
524 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
591 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
525 raise ValueError(b"deleting slices only supports a:-1 with step 1")
592 raise ValueError(b"deleting slices only supports a:-1 with step 1")
526 i = i.start
593 i = i.start
527 self._check_index(i)
594 self._check_index(i)
528 self._stripnodes(i)
595 self._stripnodes(i)
529 if i < self._lgt:
596 if i < self._lgt:
530 self._offsets = self._offsets[:i]
597 self._offsets = self._offsets[:i]
531 self._lgt = i
598 self._lgt = i
532 self._extra = []
599 self._extra = []
533 else:
600 else:
534 self._extra = self._extra[: i - self._lgt]
601 self._extra = self._extra[: i - self._lgt]
535
602
536 def _calculate_index(self, i):
603 def _calculate_index(self, i):
537 return self._offsets[i]
604 return self._offsets[i]
538
605
539
606
540 def parse_index2(data, inline, revlogv2=False):
607 def parse_index2(data, inline, revlogv2=False):
541 if not inline:
608 if not inline:
542 cls = IndexObject2 if revlogv2 else IndexObject
609 cls = IndexObject2 if revlogv2 else IndexObject
543 return cls(data), None
610 return cls(data), None
544 cls = InlinedIndexObject
611 cls = InlinedIndexObject
545 return cls(data, inline), (0, data)
612 return cls(data, inline), (0, data)
546
613
547
614
548 def parse_index_cl_v2(data):
615 def parse_index_cl_v2(data):
549 return IndexChangelogV2(data), None
616 return IndexChangelogV2(data), None
550
617
551
618
552 class IndexObject2(IndexObject):
619 class IndexObject2(IndexObject):
553 index_format = revlog_constants.INDEX_ENTRY_V2
620 index_format = revlog_constants.INDEX_ENTRY_V2
554
621
555 def replace_sidedata_info(
622 def replace_sidedata_info(
556 self,
623 self,
557 rev,
624 rev,
558 sidedata_offset,
625 sidedata_offset,
559 sidedata_length,
626 sidedata_length,
560 offset_flags,
627 offset_flags,
561 compression_mode,
628 compression_mode,
562 ):
629 ):
563 """
630 """
564 Replace an existing index entry's sidedata offset and length with new
631 Replace an existing index entry's sidedata offset and length with new
565 ones.
632 ones.
566 This cannot be used outside of the context of sidedata rewriting,
633 This cannot be used outside of the context of sidedata rewriting,
567 inside the transaction that creates the revision `rev`.
634 inside the transaction that creates the revision `rev`.
568 """
635 """
569 if rev < 0:
636 if rev < 0:
570 raise KeyError
637 raise KeyError
571 self._check_index(rev)
638 self._check_index(rev)
572 if rev < self._lgt:
639 if rev < self._lgt:
573 msg = b"cannot rewrite entries outside of this transaction"
640 msg = b"cannot rewrite entries outside of this transaction"
574 raise KeyError(msg)
641 raise KeyError(msg)
575 else:
642 else:
576 entry = list(self[rev])
643 entry = list(self[rev])
577 entry[0] = offset_flags
644 entry[0] = offset_flags
578 entry[8] = sidedata_offset
645 entry[8] = sidedata_offset
579 entry[9] = sidedata_length
646 entry[9] = sidedata_length
580 entry[11] = compression_mode
647 entry[11] = compression_mode
581 entry = tuple(entry)
648 entry = tuple(entry)
582 new = self._pack_entry(rev, entry)
649 new = self._pack_entry(rev, entry)
583 self._extra[rev - self._lgt] = new
650 self._extra[rev - self._lgt] = new
584
651
585 def _unpack_entry(self, rev, data):
652 def _unpack_entry(self, rev, data):
586 data = self.index_format.unpack(data)
653 data = self.index_format.unpack(data)
587 entry = data[:10]
654 entry = data[:10]
588 data_comp = data[10] & 3
655 data_comp = data[10] & 3
589 sidedata_comp = (data[10] & (3 << 2)) >> 2
656 sidedata_comp = (data[10] & (3 << 2)) >> 2
590 return entry + (data_comp, sidedata_comp)
657 return entry + (data_comp, sidedata_comp)
591
658
592 def _pack_entry(self, rev, entry):
659 def _pack_entry(self, rev, entry):
593 data = entry[:10]
660 data = entry[:10]
594 data_comp = entry[10] & 3
661 data_comp = entry[10] & 3
595 sidedata_comp = (entry[11] & 3) << 2
662 sidedata_comp = (entry[11] & 3) << 2
596 data += (data_comp | sidedata_comp,)
663 data += (data_comp | sidedata_comp,)
597
664
598 return self.index_format.pack(*data)
665 return self.index_format.pack(*data)
599
666
600 def entry_binary(self, rev):
667 def entry_binary(self, rev):
601 """return the raw binary string representing a revision"""
668 """return the raw binary string representing a revision"""
602 entry = self[rev]
669 entry = self[rev]
603 return self._pack_entry(rev, entry)
670 return self._pack_entry(rev, entry)
604
671
605 def pack_header(self, header):
672 def pack_header(self, header):
606 """pack header information as binary"""
673 """pack header information as binary"""
607 msg = 'version header should go in the docket, not the index: %d'
674 msg = 'version header should go in the docket, not the index: %d'
608 msg %= header
675 msg %= header
609 raise error.ProgrammingError(msg)
676 raise error.ProgrammingError(msg)
610
677
611
678
612 class IndexChangelogV2(IndexObject2):
679 class IndexChangelogV2(IndexObject2):
613 index_format = revlog_constants.INDEX_ENTRY_CL_V2
680 index_format = revlog_constants.INDEX_ENTRY_CL_V2
614
681
615 def _unpack_entry(self, rev, data, r=True):
682 def _unpack_entry(self, rev, data, r=True):
616 items = self.index_format.unpack(data)
683 items = self.index_format.unpack(data)
617 entry = items[:3] + (rev, rev) + items[3:8]
684 entry = items[:3] + (rev, rev) + items[3:8]
618 data_comp = items[8] & 3
685 data_comp = items[8] & 3
619 sidedata_comp = (items[8] >> 2) & 3
686 sidedata_comp = (items[8] >> 2) & 3
620 return entry + (data_comp, sidedata_comp)
687 return entry + (data_comp, sidedata_comp)
621
688
622 def _pack_entry(self, rev, entry):
689 def _pack_entry(self, rev, entry):
623 assert entry[3] == rev, entry[3]
690 assert entry[3] == rev, entry[3]
624 assert entry[4] == rev, entry[4]
691 assert entry[4] == rev, entry[4]
625 data = entry[:3] + entry[5:10]
692 data = entry[:3] + entry[5:10]
626 data_comp = entry[10] & 3
693 data_comp = entry[10] & 3
627 sidedata_comp = (entry[11] & 3) << 2
694 sidedata_comp = (entry[11] & 3) << 2
628 data += (data_comp | sidedata_comp,)
695 data += (data_comp | sidedata_comp,)
629 return self.index_format.pack(*data)
696 return self.index_format.pack(*data)
630
697
631
698
632 def parse_index_devel_nodemap(data, inline):
699 def parse_index_devel_nodemap(data, inline):
633 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
700 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
634 return PersistentNodeMapIndexObject(data), None
701 return PersistentNodeMapIndexObject(data), None
635
702
636
703
637 def parse_dirstate(dmap, copymap, st):
704 def parse_dirstate(dmap, copymap, st):
638 parents = [st[:20], st[20:40]]
705 parents = [st[:20], st[20:40]]
639 # dereference fields so they will be local in loop
706 # dereference fields so they will be local in loop
640 format = b">cllll"
707 format = b">cllll"
641 e_size = struct.calcsize(format)
708 e_size = struct.calcsize(format)
642 pos1 = 40
709 pos1 = 40
643 l = len(st)
710 l = len(st)
644
711
645 # the inner loop
712 # the inner loop
646 while pos1 < l:
713 while pos1 < l:
647 pos2 = pos1 + e_size
714 pos2 = pos1 + e_size
648 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
715 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
649 pos1 = pos2 + e[4]
716 pos1 = pos2 + e[4]
650 f = st[pos2:pos1]
717 f = st[pos2:pos1]
651 if b'\0' in f:
718 if b'\0' in f:
652 f, c = f.split(b'\0')
719 f, c = f.split(b'\0')
653 copymap[f] = c
720 copymap[f] = c
654 dmap[f] = DirstateItem.from_v1_data(*e[:4])
721 dmap[f] = DirstateItem.from_v1_data(*e[:4])
655 return parents
722 return parents
656
723
657
724
658 def pack_dirstate(dmap, copymap, pl, now):
725 def pack_dirstate(dmap, copymap, pl, now):
659 now = int(now)
726 now = int(now)
660 cs = stringio()
727 cs = stringio()
661 write = cs.write
728 write = cs.write
662 write(b"".join(pl))
729 write(b"".join(pl))
663 for f, e in pycompat.iteritems(dmap):
730 for f, e in pycompat.iteritems(dmap):
664 if e.need_delay(now):
731 if e.need_delay(now):
665 # The file was last modified "simultaneously" with the current
732 # The file was last modified "simultaneously" with the current
666 # write to dirstate (i.e. within the same second for file-
733 # write to dirstate (i.e. within the same second for file-
667 # systems with a granularity of 1 sec). This commonly happens
734 # systems with a granularity of 1 sec). This commonly happens
668 # for at least a couple of files on 'update'.
735 # for at least a couple of files on 'update'.
669 # The user could change the file without changing its size
736 # The user could change the file without changing its size
670 # within the same second. Invalidate the file's mtime in
737 # within the same second. Invalidate the file's mtime in
671 # dirstate, forcing future 'status' calls to compare the
738 # dirstate, forcing future 'status' calls to compare the
672 # contents of the file if the size is the same. This prevents
739 # contents of the file if the size is the same. This prevents
673 # mistakenly treating such files as clean.
740 # mistakenly treating such files as clean.
674 e.set_possibly_dirty()
741 e.set_possibly_dirty()
675
742
676 if f in copymap:
743 if f in copymap:
677 f = b"%s\0%s" % (f, copymap[f])
744 f = b"%s\0%s" % (f, copymap[f])
678 e = _pack(
745 e = _pack(
679 b">cllll",
746 b">cllll",
680 e.v1_state(),
747 e.v1_state(),
681 e.v1_mode(),
748 e.v1_mode(),
682 e.v1_size(),
749 e.v1_size(),
683 e.v1_mtime(),
750 e.v1_mtime(),
684 len(f),
751 len(f),
685 )
752 )
686 write(e)
753 write(e)
687 write(f)
754 write(f)
688 return cs.getvalue()
755 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now