##// END OF EJS Templates
dirstate-item: introduce a `maybe_clean` property...
marmoute -
r48898:80783e55 default
parent child Browse files
Show More
@@ -1,1216 +1,1233 b''
1 /*
1 /*
2 parsers.c - efficient content parsing
2 parsers.c - efficient content parsing
3
3
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #define PY_SSIZE_T_CLEAN
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
11 #include <Python.h>
12 #include <ctype.h>
12 #include <ctype.h>
13 #include <stddef.h>
13 #include <stddef.h>
14 #include <string.h>
14 #include <string.h>
15
15
16 #include "bitmanipulation.h"
16 #include "bitmanipulation.h"
17 #include "charencode.h"
17 #include "charencode.h"
18 #include "util.h"
18 #include "util.h"
19
19
20 #ifdef IS_PY3K
20 #ifdef IS_PY3K
21 /* The mapping of Python types is meant to be temporary to get Python
21 /* The mapping of Python types is meant to be temporary to get Python
22 * 3 to compile. We should remove this once Python 3 support is fully
22 * 3 to compile. We should remove this once Python 3 support is fully
23 * supported and proper types are used in the extensions themselves. */
23 * supported and proper types are used in the extensions themselves. */
24 #define PyInt_Check PyLong_Check
24 #define PyInt_Check PyLong_Check
25 #define PyInt_FromLong PyLong_FromLong
25 #define PyInt_FromLong PyLong_FromLong
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 #define PyInt_AsLong PyLong_AsLong
27 #define PyInt_AsLong PyLong_AsLong
28 #endif
28 #endif
29
29
30 static const char *const versionerrortext = "Python minor version mismatch";
30 static const char *const versionerrortext = "Python minor version mismatch";
31
31
32 static const int dirstate_v1_from_p2 = -2;
32 static const int dirstate_v1_from_p2 = -2;
33 static const int dirstate_v1_nonnormal = -1;
33 static const int dirstate_v1_nonnormal = -1;
34 static const int ambiguous_time = -1;
34 static const int ambiguous_time = -1;
35
35
36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
37 {
37 {
38 Py_ssize_t expected_size;
38 Py_ssize_t expected_size;
39
39
40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
41 return NULL;
41 return NULL;
42 }
42 }
43
43
44 return _dict_new_presized(expected_size);
44 return _dict_new_presized(expected_size);
45 }
45 }
46
46
47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
48 PyObject *kwds)
48 PyObject *kwds)
49 {
49 {
50 /* We do all the initialization here and not a tp_init function because
50 /* We do all the initialization here and not a tp_init function because
51 * dirstate_item is immutable. */
51 * dirstate_item is immutable. */
52 dirstateItemObject *t;
52 dirstateItemObject *t;
53 int wc_tracked;
53 int wc_tracked;
54 int p1_tracked;
54 int p1_tracked;
55 int p2_tracked;
55 int p2_tracked;
56 int merged;
56 int merged;
57 int clean_p1;
57 int clean_p1;
58 int clean_p2;
58 int clean_p2;
59 int possibly_dirty;
59 int possibly_dirty;
60 PyObject *parentfiledata;
60 PyObject *parentfiledata;
61 static char *keywords_name[] = {
61 static char *keywords_name[] = {
62 "wc_tracked", "p1_tracked", "p2_tracked",
62 "wc_tracked", "p1_tracked", "p2_tracked",
63 "merged", "clean_p1", "clean_p2",
63 "merged", "clean_p1", "clean_p2",
64 "possibly_dirty", "parentfiledata", NULL,
64 "possibly_dirty", "parentfiledata", NULL,
65 };
65 };
66 wc_tracked = 0;
66 wc_tracked = 0;
67 p1_tracked = 0;
67 p1_tracked = 0;
68 p2_tracked = 0;
68 p2_tracked = 0;
69 merged = 0;
69 merged = 0;
70 clean_p1 = 0;
70 clean_p1 = 0;
71 clean_p2 = 0;
71 clean_p2 = 0;
72 possibly_dirty = 0;
72 possibly_dirty = 0;
73 parentfiledata = Py_None;
73 parentfiledata = Py_None;
74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiiiO", keywords_name,
74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiiiO", keywords_name,
75 &wc_tracked, &p1_tracked, &p2_tracked,
75 &wc_tracked, &p1_tracked, &p2_tracked,
76 &merged, &clean_p1, &clean_p2,
76 &merged, &clean_p1, &clean_p2,
77 &possibly_dirty, &parentfiledata
77 &possibly_dirty, &parentfiledata
78
78
79 )) {
79 )) {
80 return NULL;
80 return NULL;
81 }
81 }
82 if (merged && (clean_p1 || clean_p2)) {
82 if (merged && (clean_p1 || clean_p2)) {
83 PyErr_SetString(PyExc_RuntimeError,
83 PyErr_SetString(PyExc_RuntimeError,
84 "`merged` argument incompatible with "
84 "`merged` argument incompatible with "
85 "`clean_p1`/`clean_p2`");
85 "`clean_p1`/`clean_p2`");
86 return NULL;
86 return NULL;
87 }
87 }
88 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
88 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
89 if (!t) {
89 if (!t) {
90 return NULL;
90 return NULL;
91 }
91 }
92
92
93 t->flags = 0;
93 t->flags = 0;
94 if (wc_tracked) {
94 if (wc_tracked) {
95 t->flags |= dirstate_flag_wc_tracked;
95 t->flags |= dirstate_flag_wc_tracked;
96 }
96 }
97 if (p1_tracked) {
97 if (p1_tracked) {
98 t->flags |= dirstate_flag_p1_tracked;
98 t->flags |= dirstate_flag_p1_tracked;
99 }
99 }
100 if (p2_tracked) {
100 if (p2_tracked) {
101 t->flags |= dirstate_flag_p2_tracked;
101 t->flags |= dirstate_flag_p2_tracked;
102 }
102 }
103 if (possibly_dirty) {
103 if (possibly_dirty) {
104 t->flags |= dirstate_flag_possibly_dirty;
104 t->flags |= dirstate_flag_possibly_dirty;
105 }
105 }
106 if (merged) {
106 if (merged) {
107 t->flags |= dirstate_flag_merged;
107 t->flags |= dirstate_flag_merged;
108 }
108 }
109 if (clean_p1) {
109 if (clean_p1) {
110 t->flags |= dirstate_flag_clean_p1;
110 t->flags |= dirstate_flag_clean_p1;
111 }
111 }
112 if (clean_p2) {
112 if (clean_p2) {
113 t->flags |= dirstate_flag_clean_p2;
113 t->flags |= dirstate_flag_clean_p2;
114 }
114 }
115 t->mode = 0;
115 t->mode = 0;
116 t->size = dirstate_v1_nonnormal;
116 t->size = dirstate_v1_nonnormal;
117 t->mtime = ambiguous_time;
117 t->mtime = ambiguous_time;
118 if (parentfiledata != Py_None) {
118 if (parentfiledata != Py_None) {
119 if (!PyTuple_CheckExact(parentfiledata)) {
119 if (!PyTuple_CheckExact(parentfiledata)) {
120 PyErr_SetString(
120 PyErr_SetString(
121 PyExc_TypeError,
121 PyExc_TypeError,
122 "parentfiledata should be a Tuple or None");
122 "parentfiledata should be a Tuple or None");
123 return NULL;
123 return NULL;
124 }
124 }
125 t->mode =
125 t->mode =
126 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
126 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
127 t->size =
127 t->size =
128 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
128 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
129 t->mtime =
129 t->mtime =
130 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
130 (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
131 }
131 }
132 return (PyObject *)t;
132 return (PyObject *)t;
133 }
133 }
134
134
135 static void dirstate_item_dealloc(PyObject *o)
135 static void dirstate_item_dealloc(PyObject *o)
136 {
136 {
137 PyObject_Del(o);
137 PyObject_Del(o);
138 }
138 }
139
139
140 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
140 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
141 {
141 {
142 return (self->flags & dirstate_flag_wc_tracked);
142 return (self->flags & dirstate_flag_wc_tracked);
143 }
143 }
144
144
145 static inline bool dirstate_item_c_added(dirstateItemObject *self)
145 static inline bool dirstate_item_c_added(dirstateItemObject *self)
146 {
146 {
147 unsigned char mask =
147 unsigned char mask =
148 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
148 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
149 dirstate_flag_p2_tracked);
149 dirstate_flag_p2_tracked);
150 unsigned char target = dirstate_flag_wc_tracked;
150 unsigned char target = dirstate_flag_wc_tracked;
151 return (self->flags & mask) == target;
151 return (self->flags & mask) == target;
152 }
152 }
153
153
154 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
154 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
155 {
155 {
156 if (self->flags & dirstate_flag_wc_tracked) {
156 if (self->flags & dirstate_flag_wc_tracked) {
157 return false;
157 return false;
158 }
158 }
159 return (self->flags &
159 return (self->flags &
160 (dirstate_flag_p1_tracked | dirstate_flag_p2_tracked));
160 (dirstate_flag_p1_tracked | dirstate_flag_p2_tracked));
161 }
161 }
162
162
163 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
163 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
164 {
164 {
165 return ((self->flags & dirstate_flag_wc_tracked) &&
165 return ((self->flags & dirstate_flag_wc_tracked) &&
166 (self->flags & dirstate_flag_merged));
166 (self->flags & dirstate_flag_merged));
167 }
167 }
168
168
169 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
169 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
170 {
170 {
171 if (!dirstate_item_c_tracked(self)) {
171 if (!dirstate_item_c_tracked(self)) {
172 return false;
172 return false;
173 }
173 }
174 return (self->flags & dirstate_flag_clean_p2);
174 return (self->flags & dirstate_flag_clean_p2);
175 }
175 }
176
176
177 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
177 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
178 {
178 {
179 if (dirstate_item_c_removed(self)) {
179 if (dirstate_item_c_removed(self)) {
180 return 'r';
180 return 'r';
181 } else if (dirstate_item_c_merged(self)) {
181 } else if (dirstate_item_c_merged(self)) {
182 return 'm';
182 return 'm';
183 } else if (dirstate_item_c_added(self)) {
183 } else if (dirstate_item_c_added(self)) {
184 return 'a';
184 return 'a';
185 } else {
185 } else {
186 return 'n';
186 return 'n';
187 }
187 }
188 }
188 }
189
189
190 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
190 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
191 {
191 {
192 return self->mode;
192 return self->mode;
193 }
193 }
194
194
195 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
195 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
196 {
196 {
197 if (dirstate_item_c_removed(self) &&
197 if (dirstate_item_c_removed(self) &&
198 (self->flags & dirstate_flag_merged)) {
198 (self->flags & dirstate_flag_merged)) {
199 return dirstate_v1_nonnormal;
199 return dirstate_v1_nonnormal;
200 } else if (dirstate_item_c_removed(self) &&
200 } else if (dirstate_item_c_removed(self) &&
201 (self->flags & dirstate_flag_clean_p2)) {
201 (self->flags & dirstate_flag_clean_p2)) {
202 return dirstate_v1_from_p2;
202 return dirstate_v1_from_p2;
203 } else if (dirstate_item_c_removed(self)) {
203 } else if (dirstate_item_c_removed(self)) {
204 return 0;
204 return 0;
205 } else if (dirstate_item_c_merged(self)) {
205 } else if (dirstate_item_c_merged(self)) {
206 return dirstate_v1_from_p2;
206 return dirstate_v1_from_p2;
207 } else if (dirstate_item_c_added(self)) {
207 } else if (dirstate_item_c_added(self)) {
208 return dirstate_v1_nonnormal;
208 return dirstate_v1_nonnormal;
209 } else if (dirstate_item_c_from_p2(self)) {
209 } else if (dirstate_item_c_from_p2(self)) {
210 return dirstate_v1_from_p2;
210 return dirstate_v1_from_p2;
211 } else if (self->flags & dirstate_flag_possibly_dirty) {
211 } else if (self->flags & dirstate_flag_possibly_dirty) {
212 return self->size; /* NON NORMAL ? */
212 return self->size; /* NON NORMAL ? */
213 } else {
213 } else {
214 return self->size;
214 return self->size;
215 }
215 }
216 }
216 }
217
217
218 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
218 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
219 {
219 {
220 if (dirstate_item_c_removed(self)) {
220 if (dirstate_item_c_removed(self)) {
221 return 0;
221 return 0;
222 } else if (self->flags & dirstate_flag_possibly_dirty) {
222 } else if (self->flags & dirstate_flag_possibly_dirty) {
223 return ambiguous_time;
223 return ambiguous_time;
224 } else if (dirstate_item_c_merged(self)) {
224 } else if (dirstate_item_c_merged(self)) {
225 return ambiguous_time;
225 return ambiguous_time;
226 } else if (dirstate_item_c_added(self)) {
226 } else if (dirstate_item_c_added(self)) {
227 return ambiguous_time;
227 return ambiguous_time;
228 } else if (dirstate_item_c_from_p2(self)) {
228 } else if (dirstate_item_c_from_p2(self)) {
229 return ambiguous_time;
229 return ambiguous_time;
230 } else {
230 } else {
231 return self->mtime;
231 return self->mtime;
232 }
232 }
233 }
233 }
234
234
235 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
235 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
236 {
236 {
237 char state = dirstate_item_c_v1_state(self);
237 char state = dirstate_item_c_v1_state(self);
238 return PyBytes_FromStringAndSize(&state, 1);
238 return PyBytes_FromStringAndSize(&state, 1);
239 };
239 };
240
240
241 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
241 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
242 {
242 {
243 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
243 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
244 };
244 };
245
245
246 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
246 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
247 {
247 {
248 return PyInt_FromLong(dirstate_item_c_v1_size(self));
248 return PyInt_FromLong(dirstate_item_c_v1_size(self));
249 };
249 };
250
250
251 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
251 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
252 {
252 {
253 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
253 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
254 };
254 };
255
255
256 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
256 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
257 PyObject *value)
257 PyObject *value)
258 {
258 {
259 long now;
259 long now;
260 if (!pylong_to_long(value, &now)) {
260 if (!pylong_to_long(value, &now)) {
261 return NULL;
261 return NULL;
262 }
262 }
263 if (dirstate_item_c_v1_state(self) == 'n' &&
263 if (dirstate_item_c_v1_state(self) == 'n' &&
264 dirstate_item_c_v1_mtime(self) == now) {
264 dirstate_item_c_v1_mtime(self) == now) {
265 Py_RETURN_TRUE;
265 Py_RETURN_TRUE;
266 } else {
266 } else {
267 Py_RETURN_FALSE;
267 Py_RETURN_FALSE;
268 }
268 }
269 };
269 };
270
270
271 /* This will never change since it's bound to V1
271 /* This will never change since it's bound to V1
272 */
272 */
273 static inline dirstateItemObject *
273 static inline dirstateItemObject *
274 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
274 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
275 {
275 {
276 dirstateItemObject *t =
276 dirstateItemObject *t =
277 PyObject_New(dirstateItemObject, &dirstateItemType);
277 PyObject_New(dirstateItemObject, &dirstateItemType);
278 if (!t) {
278 if (!t) {
279 return NULL;
279 return NULL;
280 }
280 }
281
281
282 if (state == 'm') {
282 if (state == 'm') {
283 t->flags =
283 t->flags =
284 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
284 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
285 dirstate_flag_p2_tracked | dirstate_flag_merged);
285 dirstate_flag_p2_tracked | dirstate_flag_merged);
286 t->mode = 0;
286 t->mode = 0;
287 t->size = dirstate_v1_from_p2;
287 t->size = dirstate_v1_from_p2;
288 t->mtime = ambiguous_time;
288 t->mtime = ambiguous_time;
289 } else if (state == 'a') {
289 } else if (state == 'a') {
290 t->flags = dirstate_flag_wc_tracked;
290 t->flags = dirstate_flag_wc_tracked;
291 t->mode = 0;
291 t->mode = 0;
292 t->size = dirstate_v1_nonnormal;
292 t->size = dirstate_v1_nonnormal;
293 t->mtime = ambiguous_time;
293 t->mtime = ambiguous_time;
294 } else if (state == 'r') {
294 } else if (state == 'r') {
295 t->mode = 0;
295 t->mode = 0;
296 t->size = 0;
296 t->size = 0;
297 t->mtime = 0;
297 t->mtime = 0;
298 if (size == dirstate_v1_nonnormal) {
298 if (size == dirstate_v1_nonnormal) {
299 t->flags =
299 t->flags =
300 (dirstate_flag_p1_tracked |
300 (dirstate_flag_p1_tracked |
301 dirstate_flag_p2_tracked | dirstate_flag_merged);
301 dirstate_flag_p2_tracked | dirstate_flag_merged);
302 } else if (size == dirstate_v1_from_p2) {
302 } else if (size == dirstate_v1_from_p2) {
303 t->flags =
303 t->flags =
304 (dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
304 (dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
305 } else {
305 } else {
306 t->flags = dirstate_flag_p1_tracked;
306 t->flags = dirstate_flag_p1_tracked;
307 }
307 }
308 } else if (state == 'n') {
308 } else if (state == 'n') {
309 if (size == dirstate_v1_from_p2) {
309 if (size == dirstate_v1_from_p2) {
310 t->flags =
310 t->flags =
311 (dirstate_flag_wc_tracked |
311 (dirstate_flag_wc_tracked |
312 dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
312 dirstate_flag_p2_tracked | dirstate_flag_clean_p2);
313 t->mode = 0;
313 t->mode = 0;
314 t->size = dirstate_v1_from_p2;
314 t->size = dirstate_v1_from_p2;
315 t->mtime = ambiguous_time;
315 t->mtime = ambiguous_time;
316 } else if (size == dirstate_v1_nonnormal) {
316 } else if (size == dirstate_v1_nonnormal) {
317 t->flags = (dirstate_flag_wc_tracked |
317 t->flags = (dirstate_flag_wc_tracked |
318 dirstate_flag_p1_tracked |
318 dirstate_flag_p1_tracked |
319 dirstate_flag_possibly_dirty);
319 dirstate_flag_possibly_dirty);
320 t->mode = 0;
320 t->mode = 0;
321 t->size = dirstate_v1_nonnormal;
321 t->size = dirstate_v1_nonnormal;
322 t->mtime = ambiguous_time;
322 t->mtime = ambiguous_time;
323 } else if (mtime == ambiguous_time) {
323 } else if (mtime == ambiguous_time) {
324 t->flags = (dirstate_flag_wc_tracked |
324 t->flags = (dirstate_flag_wc_tracked |
325 dirstate_flag_p1_tracked |
325 dirstate_flag_p1_tracked |
326 dirstate_flag_possibly_dirty);
326 dirstate_flag_possibly_dirty);
327 t->mode = mode;
327 t->mode = mode;
328 t->size = size;
328 t->size = size;
329 t->mtime = 0;
329 t->mtime = 0;
330 } else {
330 } else {
331 t->flags = (dirstate_flag_wc_tracked |
331 t->flags = (dirstate_flag_wc_tracked |
332 dirstate_flag_p1_tracked);
332 dirstate_flag_p1_tracked);
333 t->mode = mode;
333 t->mode = mode;
334 t->size = size;
334 t->size = size;
335 t->mtime = mtime;
335 t->mtime = mtime;
336 }
336 }
337 } else {
337 } else {
338 PyErr_Format(PyExc_RuntimeError,
338 PyErr_Format(PyExc_RuntimeError,
339 "unknown state: `%c` (%d, %d, %d)", state, mode,
339 "unknown state: `%c` (%d, %d, %d)", state, mode,
340 size, mtime, NULL);
340 size, mtime, NULL);
341 Py_DECREF(t);
341 Py_DECREF(t);
342 return NULL;
342 return NULL;
343 }
343 }
344
344
345 return t;
345 return t;
346 }
346 }
347
347
348 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
348 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
349 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
349 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
350 PyObject *args)
350 PyObject *args)
351 {
351 {
352 /* We do all the initialization here and not a tp_init function because
352 /* We do all the initialization here and not a tp_init function because
353 * dirstate_item is immutable. */
353 * dirstate_item is immutable. */
354 char state;
354 char state;
355 int size, mode, mtime;
355 int size, mode, mtime;
356 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
356 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
357 return NULL;
357 return NULL;
358 }
358 }
359 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
359 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
360 };
360 };
361
361
362 /* constructor to help legacy API to build a new "added" item
362 /* constructor to help legacy API to build a new "added" item
363
363
364 Should eventually be removed */
364 Should eventually be removed */
365 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
365 static PyObject *dirstate_item_new_added(PyTypeObject *subtype)
366 {
366 {
367 dirstateItemObject *t;
367 dirstateItemObject *t;
368 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
368 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
369 if (!t) {
369 if (!t) {
370 return NULL;
370 return NULL;
371 }
371 }
372 t->flags = dirstate_flag_wc_tracked;
372 t->flags = dirstate_flag_wc_tracked;
373 t->mode = 0;
373 t->mode = 0;
374 t->size = dirstate_v1_nonnormal;
374 t->size = dirstate_v1_nonnormal;
375 t->mtime = ambiguous_time;
375 t->mtime = ambiguous_time;
376 return (PyObject *)t;
376 return (PyObject *)t;
377 };
377 };
378
378
379 /* constructor to help legacy API to build a new "merged" item
379 /* constructor to help legacy API to build a new "merged" item
380
380
381 Should eventually be removed */
381 Should eventually be removed */
382 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
382 static PyObject *dirstate_item_new_merged(PyTypeObject *subtype)
383 {
383 {
384 dirstateItemObject *t;
384 dirstateItemObject *t;
385 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
385 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
386 if (!t) {
386 if (!t) {
387 return NULL;
387 return NULL;
388 }
388 }
389 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
389 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
390 dirstate_flag_p2_tracked | dirstate_flag_merged);
390 dirstate_flag_p2_tracked | dirstate_flag_merged);
391 t->mode = 0;
391 t->mode = 0;
392 t->size = dirstate_v1_from_p2;
392 t->size = dirstate_v1_from_p2;
393 t->mtime = ambiguous_time;
393 t->mtime = ambiguous_time;
394 return (PyObject *)t;
394 return (PyObject *)t;
395 };
395 };
396
396
397 /* constructor to help legacy API to build a new "from_p2" item
397 /* constructor to help legacy API to build a new "from_p2" item
398
398
399 Should eventually be removed */
399 Should eventually be removed */
400 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
400 static PyObject *dirstate_item_new_from_p2(PyTypeObject *subtype)
401 {
401 {
402 /* We do all the initialization here and not a tp_init function because
402 /* We do all the initialization here and not a tp_init function because
403 * dirstate_item is immutable. */
403 * dirstate_item is immutable. */
404 dirstateItemObject *t;
404 dirstateItemObject *t;
405 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
405 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
406 if (!t) {
406 if (!t) {
407 return NULL;
407 return NULL;
408 }
408 }
409 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p2_tracked |
409 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p2_tracked |
410 dirstate_flag_clean_p2);
410 dirstate_flag_clean_p2);
411 t->mode = 0;
411 t->mode = 0;
412 t->size = dirstate_v1_from_p2;
412 t->size = dirstate_v1_from_p2;
413 t->mtime = ambiguous_time;
413 t->mtime = ambiguous_time;
414 return (PyObject *)t;
414 return (PyObject *)t;
415 };
415 };
416
416
417 /* constructor to help legacy API to build a new "possibly" item
417 /* constructor to help legacy API to build a new "possibly" item
418
418
419 Should eventually be removed */
419 Should eventually be removed */
420 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
420 static PyObject *dirstate_item_new_possibly_dirty(PyTypeObject *subtype)
421 {
421 {
422 /* We do all the initialization here and not a tp_init function because
422 /* We do all the initialization here and not a tp_init function because
423 * dirstate_item is immutable. */
423 * dirstate_item is immutable. */
424 dirstateItemObject *t;
424 dirstateItemObject *t;
425 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
425 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
426 if (!t) {
426 if (!t) {
427 return NULL;
427 return NULL;
428 }
428 }
429 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
429 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
430 dirstate_flag_possibly_dirty);
430 dirstate_flag_possibly_dirty);
431 t->mode = 0;
431 t->mode = 0;
432 t->size = dirstate_v1_nonnormal;
432 t->size = dirstate_v1_nonnormal;
433 t->mtime = ambiguous_time;
433 t->mtime = ambiguous_time;
434 return (PyObject *)t;
434 return (PyObject *)t;
435 };
435 };
436
436
437 /* constructor to help legacy API to build a new "normal" item
437 /* constructor to help legacy API to build a new "normal" item
438
438
439 Should eventually be removed */
439 Should eventually be removed */
440 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
440 static PyObject *dirstate_item_new_normal(PyTypeObject *subtype, PyObject *args)
441 {
441 {
442 /* We do all the initialization here and not a tp_init function because
442 /* We do all the initialization here and not a tp_init function because
443 * dirstate_item is immutable. */
443 * dirstate_item is immutable. */
444 dirstateItemObject *t;
444 dirstateItemObject *t;
445 int size, mode, mtime;
445 int size, mode, mtime;
446 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
446 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
447 return NULL;
447 return NULL;
448 }
448 }
449
449
450 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
450 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
451 if (!t) {
451 if (!t) {
452 return NULL;
452 return NULL;
453 }
453 }
454 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked);
454 t->flags = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked);
455 t->mode = mode;
455 t->mode = mode;
456 t->size = size;
456 t->size = size;
457 t->mtime = mtime;
457 t->mtime = mtime;
458 return (PyObject *)t;
458 return (PyObject *)t;
459 };
459 };
460
460
461 /* This means the next status call will have to actually check its content
461 /* This means the next status call will have to actually check its content
462 to make sure it is correct. */
462 to make sure it is correct. */
463 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
463 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
464 {
464 {
465 self->flags |= dirstate_flag_possibly_dirty;
465 self->flags |= dirstate_flag_possibly_dirty;
466 Py_RETURN_NONE;
466 Py_RETURN_NONE;
467 }
467 }
468
468
469 /* See docstring of the python implementation for details */
469 /* See docstring of the python implementation for details */
470 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
470 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
471 PyObject *args)
471 PyObject *args)
472 {
472 {
473 int size, mode, mtime;
473 int size, mode, mtime;
474 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
474 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
475 return NULL;
475 return NULL;
476 }
476 }
477 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
477 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
478 self->mode = mode;
478 self->mode = mode;
479 self->size = size;
479 self->size = size;
480 self->mtime = mtime;
480 self->mtime = mtime;
481 Py_RETURN_NONE;
481 Py_RETURN_NONE;
482 }
482 }
483
483
484 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
484 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
485 {
485 {
486 self->flags |= dirstate_flag_wc_tracked;
486 self->flags |= dirstate_flag_wc_tracked;
487 self->flags |= dirstate_flag_possibly_dirty;
487 self->flags |= dirstate_flag_possibly_dirty;
488 /* size = None on the python size turn into size = NON_NORMAL when
488 /* size = None on the python size turn into size = NON_NORMAL when
489 * accessed. So the next line is currently required, but a some future
489 * accessed. So the next line is currently required, but a some future
490 * clean up would be welcome. */
490 * clean up would be welcome. */
491 self->size = dirstate_v1_nonnormal;
491 self->size = dirstate_v1_nonnormal;
492 Py_RETURN_NONE;
492 Py_RETURN_NONE;
493 }
493 }
494
494
495 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
495 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
496 {
496 {
497 self->flags &= ~dirstate_flag_wc_tracked;
497 self->flags &= ~dirstate_flag_wc_tracked;
498 self->mode = 0;
498 self->mode = 0;
499 self->mtime = 0;
499 self->mtime = 0;
500 self->size = 0;
500 self->size = 0;
501 Py_RETURN_NONE;
501 Py_RETURN_NONE;
502 }
502 }
503
503
504 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
504 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
505 {
505 {
506 if (dirstate_item_c_merged(self) || dirstate_item_c_from_p2(self)) {
506 if (dirstate_item_c_merged(self) || dirstate_item_c_from_p2(self)) {
507 if (dirstate_item_c_merged(self)) {
507 if (dirstate_item_c_merged(self)) {
508 self->flags |= dirstate_flag_p1_tracked;
508 self->flags |= dirstate_flag_p1_tracked;
509 } else {
509 } else {
510 self->flags &= ~dirstate_flag_p1_tracked;
510 self->flags &= ~dirstate_flag_p1_tracked;
511 }
511 }
512 self->flags &=
512 self->flags &=
513 ~(dirstate_flag_merged | dirstate_flag_clean_p1 |
513 ~(dirstate_flag_merged | dirstate_flag_clean_p1 |
514 dirstate_flag_clean_p2 | dirstate_flag_p2_tracked);
514 dirstate_flag_clean_p2 | dirstate_flag_p2_tracked);
515 self->flags |= dirstate_flag_possibly_dirty;
515 self->flags |= dirstate_flag_possibly_dirty;
516 self->mode = 0;
516 self->mode = 0;
517 self->mtime = 0;
517 self->mtime = 0;
518 /* size = None on the python size turn into size = NON_NORMAL
518 /* size = None on the python size turn into size = NON_NORMAL
519 * when accessed. So the next line is currently required, but a
519 * when accessed. So the next line is currently required, but a
520 * some future clean up would be welcome. */
520 * some future clean up would be welcome. */
521 self->size = dirstate_v1_nonnormal;
521 self->size = dirstate_v1_nonnormal;
522 }
522 }
523 Py_RETURN_NONE;
523 Py_RETURN_NONE;
524 }
524 }
525 static PyMethodDef dirstate_item_methods[] = {
525 static PyMethodDef dirstate_item_methods[] = {
526 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
526 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
527 "return a \"state\" suitable for v1 serialization"},
527 "return a \"state\" suitable for v1 serialization"},
528 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
528 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
529 "return a \"mode\" suitable for v1 serialization"},
529 "return a \"mode\" suitable for v1 serialization"},
530 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
530 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
531 "return a \"size\" suitable for v1 serialization"},
531 "return a \"size\" suitable for v1 serialization"},
532 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
532 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
533 "return a \"mtime\" suitable for v1 serialization"},
533 "return a \"mtime\" suitable for v1 serialization"},
534 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
534 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
535 "True if the stored mtime would be ambiguous with the current time"},
535 "True if the stored mtime would be ambiguous with the current time"},
536 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
536 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
537 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
537 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
538 {"new_added", (PyCFunction)dirstate_item_new_added,
538 {"new_added", (PyCFunction)dirstate_item_new_added,
539 METH_NOARGS | METH_CLASS,
539 METH_NOARGS | METH_CLASS,
540 "constructor to help legacy API to build a new \"added\" item"},
540 "constructor to help legacy API to build a new \"added\" item"},
541 {"new_merged", (PyCFunction)dirstate_item_new_merged,
541 {"new_merged", (PyCFunction)dirstate_item_new_merged,
542 METH_NOARGS | METH_CLASS,
542 METH_NOARGS | METH_CLASS,
543 "constructor to help legacy API to build a new \"merged\" item"},
543 "constructor to help legacy API to build a new \"merged\" item"},
544 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
544 {"new_from_p2", (PyCFunction)dirstate_item_new_from_p2,
545 METH_NOARGS | METH_CLASS,
545 METH_NOARGS | METH_CLASS,
546 "constructor to help legacy API to build a new \"from_p2\" item"},
546 "constructor to help legacy API to build a new \"from_p2\" item"},
547 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
547 {"new_possibly_dirty", (PyCFunction)dirstate_item_new_possibly_dirty,
548 METH_NOARGS | METH_CLASS,
548 METH_NOARGS | METH_CLASS,
549 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
549 "constructor to help legacy API to build a new \"possibly_dirty\" item"},
550 {"new_normal", (PyCFunction)dirstate_item_new_normal,
550 {"new_normal", (PyCFunction)dirstate_item_new_normal,
551 METH_VARARGS | METH_CLASS,
551 METH_VARARGS | METH_CLASS,
552 "constructor to help legacy API to build a new \"normal\" item"},
552 "constructor to help legacy API to build a new \"normal\" item"},
553 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
553 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
554 METH_NOARGS, "mark a file as \"possibly dirty\""},
554 METH_NOARGS, "mark a file as \"possibly dirty\""},
555 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
555 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
556 "mark a file as \"clean\""},
556 "mark a file as \"clean\""},
557 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
557 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
558 "mark a file as \"tracked\""},
558 "mark a file as \"tracked\""},
559 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
559 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
560 "mark a file as \"untracked\""},
560 "mark a file as \"untracked\""},
561 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
561 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
562 "remove all \"merge-only\" from a DirstateItem"},
562 "remove all \"merge-only\" from a DirstateItem"},
563 {NULL} /* Sentinel */
563 {NULL} /* Sentinel */
564 };
564 };
565
565
566 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
566 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
567 {
567 {
568 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
568 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
569 };
569 };
570
570
571 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
571 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
572 {
572 {
573 return PyInt_FromLong(dirstate_item_c_v1_size(self));
573 return PyInt_FromLong(dirstate_item_c_v1_size(self));
574 };
574 };
575
575
576 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
576 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
577 {
577 {
578 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
578 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
579 };
579 };
580
580
581 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
581 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
582 {
582 {
583 char state = dirstate_item_c_v1_state(self);
583 char state = dirstate_item_c_v1_state(self);
584 return PyBytes_FromStringAndSize(&state, 1);
584 return PyBytes_FromStringAndSize(&state, 1);
585 };
585 };
586
586
587 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
587 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
588 {
588 {
589 if (dirstate_item_c_tracked(self)) {
589 if (dirstate_item_c_tracked(self)) {
590 Py_RETURN_TRUE;
590 Py_RETURN_TRUE;
591 } else {
591 } else {
592 Py_RETURN_FALSE;
592 Py_RETURN_FALSE;
593 }
593 }
594 };
594 };
595
595
596 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
596 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
597 {
597 {
598 if (dirstate_item_c_added(self)) {
598 if (dirstate_item_c_added(self)) {
599 Py_RETURN_TRUE;
599 Py_RETURN_TRUE;
600 } else {
600 } else {
601 Py_RETURN_FALSE;
601 Py_RETURN_FALSE;
602 }
602 }
603 };
603 };
604
604
605 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
605 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
606 {
606 {
607 if (dirstate_item_c_merged(self)) {
607 if (dirstate_item_c_merged(self)) {
608 Py_RETURN_TRUE;
608 Py_RETURN_TRUE;
609 } else {
609 } else {
610 Py_RETURN_FALSE;
610 Py_RETURN_FALSE;
611 }
611 }
612 };
612 };
613
613
614 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
614 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
615 {
615 {
616 if (dirstate_item_c_from_p2(self)) {
616 if (dirstate_item_c_from_p2(self)) {
617 Py_RETURN_TRUE;
617 Py_RETURN_TRUE;
618 } else {
618 } else {
619 Py_RETURN_FALSE;
619 Py_RETURN_FALSE;
620 }
620 }
621 };
621 };
622
622
623 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
624 {
625 if (!(self->flags & dirstate_flag_wc_tracked)) {
626 Py_RETURN_FALSE;
627 } else if (dirstate_item_c_added(self)) {
628 Py_RETURN_FALSE;
629 } else if (self->flags & dirstate_flag_merged) {
630 Py_RETURN_FALSE;
631 } else if (self->flags & dirstate_flag_clean_p2) {
632 Py_RETURN_FALSE;
633 } else {
634 Py_RETURN_TRUE;
635 }
636 };
637
623 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
638 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
624 {
639 {
625 if (dirstate_item_c_removed(self)) {
640 if (dirstate_item_c_removed(self)) {
626 Py_RETURN_TRUE;
641 Py_RETURN_TRUE;
627 } else {
642 } else {
628 Py_RETURN_FALSE;
643 Py_RETURN_FALSE;
629 }
644 }
630 };
645 };
631
646
632 static PyGetSetDef dirstate_item_getset[] = {
647 static PyGetSetDef dirstate_item_getset[] = {
633 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
648 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
634 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
649 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
635 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
650 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
636 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
651 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
637 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
652 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
638 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
653 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
639 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
654 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
640 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
655 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
656 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
657 NULL},
641 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
658 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
642 {NULL} /* Sentinel */
659 {NULL} /* Sentinel */
643 };
660 };
644
661
645 PyTypeObject dirstateItemType = {
662 PyTypeObject dirstateItemType = {
646 PyVarObject_HEAD_INIT(NULL, 0) /* header */
663 PyVarObject_HEAD_INIT(NULL, 0) /* header */
647 "dirstate_tuple", /* tp_name */
664 "dirstate_tuple", /* tp_name */
648 sizeof(dirstateItemObject), /* tp_basicsize */
665 sizeof(dirstateItemObject), /* tp_basicsize */
649 0, /* tp_itemsize */
666 0, /* tp_itemsize */
650 (destructor)dirstate_item_dealloc, /* tp_dealloc */
667 (destructor)dirstate_item_dealloc, /* tp_dealloc */
651 0, /* tp_print */
668 0, /* tp_print */
652 0, /* tp_getattr */
669 0, /* tp_getattr */
653 0, /* tp_setattr */
670 0, /* tp_setattr */
654 0, /* tp_compare */
671 0, /* tp_compare */
655 0, /* tp_repr */
672 0, /* tp_repr */
656 0, /* tp_as_number */
673 0, /* tp_as_number */
657 0, /* tp_as_sequence */
674 0, /* tp_as_sequence */
658 0, /* tp_as_mapping */
675 0, /* tp_as_mapping */
659 0, /* tp_hash */
676 0, /* tp_hash */
660 0, /* tp_call */
677 0, /* tp_call */
661 0, /* tp_str */
678 0, /* tp_str */
662 0, /* tp_getattro */
679 0, /* tp_getattro */
663 0, /* tp_setattro */
680 0, /* tp_setattro */
664 0, /* tp_as_buffer */
681 0, /* tp_as_buffer */
665 Py_TPFLAGS_DEFAULT, /* tp_flags */
682 Py_TPFLAGS_DEFAULT, /* tp_flags */
666 "dirstate tuple", /* tp_doc */
683 "dirstate tuple", /* tp_doc */
667 0, /* tp_traverse */
684 0, /* tp_traverse */
668 0, /* tp_clear */
685 0, /* tp_clear */
669 0, /* tp_richcompare */
686 0, /* tp_richcompare */
670 0, /* tp_weaklistoffset */
687 0, /* tp_weaklistoffset */
671 0, /* tp_iter */
688 0, /* tp_iter */
672 0, /* tp_iternext */
689 0, /* tp_iternext */
673 dirstate_item_methods, /* tp_methods */
690 dirstate_item_methods, /* tp_methods */
674 0, /* tp_members */
691 0, /* tp_members */
675 dirstate_item_getset, /* tp_getset */
692 dirstate_item_getset, /* tp_getset */
676 0, /* tp_base */
693 0, /* tp_base */
677 0, /* tp_dict */
694 0, /* tp_dict */
678 0, /* tp_descr_get */
695 0, /* tp_descr_get */
679 0, /* tp_descr_set */
696 0, /* tp_descr_set */
680 0, /* tp_dictoffset */
697 0, /* tp_dictoffset */
681 0, /* tp_init */
698 0, /* tp_init */
682 0, /* tp_alloc */
699 0, /* tp_alloc */
683 dirstate_item_new, /* tp_new */
700 dirstate_item_new, /* tp_new */
684 };
701 };
685
702
686 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
703 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
687 {
704 {
688 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
705 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
689 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
706 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
690 char state, *cur, *str, *cpos;
707 char state, *cur, *str, *cpos;
691 int mode, size, mtime;
708 int mode, size, mtime;
692 unsigned int flen, pos = 40;
709 unsigned int flen, pos = 40;
693 Py_ssize_t len = 40;
710 Py_ssize_t len = 40;
694 Py_ssize_t readlen;
711 Py_ssize_t readlen;
695
712
696 if (!PyArg_ParseTuple(
713 if (!PyArg_ParseTuple(
697 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
714 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
698 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
715 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
699 goto quit;
716 goto quit;
700 }
717 }
701
718
702 len = readlen;
719 len = readlen;
703
720
704 /* read parents */
721 /* read parents */
705 if (len < 40) {
722 if (len < 40) {
706 PyErr_SetString(PyExc_ValueError,
723 PyErr_SetString(PyExc_ValueError,
707 "too little data for parents");
724 "too little data for parents");
708 goto quit;
725 goto quit;
709 }
726 }
710
727
711 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
728 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
712 str + 20, (Py_ssize_t)20);
729 str + 20, (Py_ssize_t)20);
713 if (!parents) {
730 if (!parents) {
714 goto quit;
731 goto quit;
715 }
732 }
716
733
717 /* read filenames */
734 /* read filenames */
718 while (pos >= 40 && pos < len) {
735 while (pos >= 40 && pos < len) {
719 if (pos + 17 > len) {
736 if (pos + 17 > len) {
720 PyErr_SetString(PyExc_ValueError,
737 PyErr_SetString(PyExc_ValueError,
721 "overflow in dirstate");
738 "overflow in dirstate");
722 goto quit;
739 goto quit;
723 }
740 }
724 cur = str + pos;
741 cur = str + pos;
725 /* unpack header */
742 /* unpack header */
726 state = *cur;
743 state = *cur;
727 mode = getbe32(cur + 1);
744 mode = getbe32(cur + 1);
728 size = getbe32(cur + 5);
745 size = getbe32(cur + 5);
729 mtime = getbe32(cur + 9);
746 mtime = getbe32(cur + 9);
730 flen = getbe32(cur + 13);
747 flen = getbe32(cur + 13);
731 pos += 17;
748 pos += 17;
732 cur += 17;
749 cur += 17;
733 if (flen > len - pos) {
750 if (flen > len - pos) {
734 PyErr_SetString(PyExc_ValueError,
751 PyErr_SetString(PyExc_ValueError,
735 "overflow in dirstate");
752 "overflow in dirstate");
736 goto quit;
753 goto quit;
737 }
754 }
738
755
739 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
756 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
740 size, mtime);
757 size, mtime);
741 if (!entry)
758 if (!entry)
742 goto quit;
759 goto quit;
743 cpos = memchr(cur, 0, flen);
760 cpos = memchr(cur, 0, flen);
744 if (cpos) {
761 if (cpos) {
745 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
762 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
746 cname = PyBytes_FromStringAndSize(
763 cname = PyBytes_FromStringAndSize(
747 cpos + 1, flen - (cpos - cur) - 1);
764 cpos + 1, flen - (cpos - cur) - 1);
748 if (!fname || !cname ||
765 if (!fname || !cname ||
749 PyDict_SetItem(cmap, fname, cname) == -1 ||
766 PyDict_SetItem(cmap, fname, cname) == -1 ||
750 PyDict_SetItem(dmap, fname, entry) == -1) {
767 PyDict_SetItem(dmap, fname, entry) == -1) {
751 goto quit;
768 goto quit;
752 }
769 }
753 Py_DECREF(cname);
770 Py_DECREF(cname);
754 } else {
771 } else {
755 fname = PyBytes_FromStringAndSize(cur, flen);
772 fname = PyBytes_FromStringAndSize(cur, flen);
756 if (!fname ||
773 if (!fname ||
757 PyDict_SetItem(dmap, fname, entry) == -1) {
774 PyDict_SetItem(dmap, fname, entry) == -1) {
758 goto quit;
775 goto quit;
759 }
776 }
760 }
777 }
761 Py_DECREF(fname);
778 Py_DECREF(fname);
762 Py_DECREF(entry);
779 Py_DECREF(entry);
763 fname = cname = entry = NULL;
780 fname = cname = entry = NULL;
764 pos += flen;
781 pos += flen;
765 }
782 }
766
783
767 ret = parents;
784 ret = parents;
768 Py_INCREF(ret);
785 Py_INCREF(ret);
769 quit:
786 quit:
770 Py_XDECREF(fname);
787 Py_XDECREF(fname);
771 Py_XDECREF(cname);
788 Py_XDECREF(cname);
772 Py_XDECREF(entry);
789 Py_XDECREF(entry);
773 Py_XDECREF(parents);
790 Py_XDECREF(parents);
774 return ret;
791 return ret;
775 }
792 }
776
793
777 /*
794 /*
778 * Efficiently pack a dirstate object into its on-disk format.
795 * Efficiently pack a dirstate object into its on-disk format.
779 */
796 */
780 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
797 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
781 {
798 {
782 PyObject *packobj = NULL;
799 PyObject *packobj = NULL;
783 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
800 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
784 Py_ssize_t nbytes, pos, l;
801 Py_ssize_t nbytes, pos, l;
785 PyObject *k, *v = NULL, *pn;
802 PyObject *k, *v = NULL, *pn;
786 char *p, *s;
803 char *p, *s;
787 int now;
804 int now;
788
805
789 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
806 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
790 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
807 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
791 &now)) {
808 &now)) {
792 return NULL;
809 return NULL;
793 }
810 }
794
811
795 if (PyTuple_Size(pl) != 2) {
812 if (PyTuple_Size(pl) != 2) {
796 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
813 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
797 return NULL;
814 return NULL;
798 }
815 }
799
816
800 /* Figure out how much we need to allocate. */
817 /* Figure out how much we need to allocate. */
801 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
818 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
802 PyObject *c;
819 PyObject *c;
803 if (!PyBytes_Check(k)) {
820 if (!PyBytes_Check(k)) {
804 PyErr_SetString(PyExc_TypeError, "expected string key");
821 PyErr_SetString(PyExc_TypeError, "expected string key");
805 goto bail;
822 goto bail;
806 }
823 }
807 nbytes += PyBytes_GET_SIZE(k) + 17;
824 nbytes += PyBytes_GET_SIZE(k) + 17;
808 c = PyDict_GetItem(copymap, k);
825 c = PyDict_GetItem(copymap, k);
809 if (c) {
826 if (c) {
810 if (!PyBytes_Check(c)) {
827 if (!PyBytes_Check(c)) {
811 PyErr_SetString(PyExc_TypeError,
828 PyErr_SetString(PyExc_TypeError,
812 "expected string key");
829 "expected string key");
813 goto bail;
830 goto bail;
814 }
831 }
815 nbytes += PyBytes_GET_SIZE(c) + 1;
832 nbytes += PyBytes_GET_SIZE(c) + 1;
816 }
833 }
817 }
834 }
818
835
819 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
836 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
820 if (packobj == NULL) {
837 if (packobj == NULL) {
821 goto bail;
838 goto bail;
822 }
839 }
823
840
824 p = PyBytes_AS_STRING(packobj);
841 p = PyBytes_AS_STRING(packobj);
825
842
826 pn = PyTuple_GET_ITEM(pl, 0);
843 pn = PyTuple_GET_ITEM(pl, 0);
827 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
844 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
828 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
845 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
829 goto bail;
846 goto bail;
830 }
847 }
831 memcpy(p, s, l);
848 memcpy(p, s, l);
832 p += 20;
849 p += 20;
833 pn = PyTuple_GET_ITEM(pl, 1);
850 pn = PyTuple_GET_ITEM(pl, 1);
834 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
851 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
835 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
852 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
836 goto bail;
853 goto bail;
837 }
854 }
838 memcpy(p, s, l);
855 memcpy(p, s, l);
839 p += 20;
856 p += 20;
840
857
841 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
858 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
842 dirstateItemObject *tuple;
859 dirstateItemObject *tuple;
843 char state;
860 char state;
844 int mode, size, mtime;
861 int mode, size, mtime;
845 Py_ssize_t len, l;
862 Py_ssize_t len, l;
846 PyObject *o;
863 PyObject *o;
847 char *t;
864 char *t;
848
865
849 if (!dirstate_tuple_check(v)) {
866 if (!dirstate_tuple_check(v)) {
850 PyErr_SetString(PyExc_TypeError,
867 PyErr_SetString(PyExc_TypeError,
851 "expected a dirstate tuple");
868 "expected a dirstate tuple");
852 goto bail;
869 goto bail;
853 }
870 }
854 tuple = (dirstateItemObject *)v;
871 tuple = (dirstateItemObject *)v;
855
872
856 state = dirstate_item_c_v1_state(tuple);
873 state = dirstate_item_c_v1_state(tuple);
857 mode = dirstate_item_c_v1_mode(tuple);
874 mode = dirstate_item_c_v1_mode(tuple);
858 size = dirstate_item_c_v1_size(tuple);
875 size = dirstate_item_c_v1_size(tuple);
859 mtime = dirstate_item_c_v1_mtime(tuple);
876 mtime = dirstate_item_c_v1_mtime(tuple);
860 if (state == 'n' && mtime == now) {
877 if (state == 'n' && mtime == now) {
861 /* See pure/parsers.py:pack_dirstate for why we do
878 /* See pure/parsers.py:pack_dirstate for why we do
862 * this. */
879 * this. */
863 mtime = -1;
880 mtime = -1;
864 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
881 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
865 state, mode, size, mtime);
882 state, mode, size, mtime);
866 if (!mtime_unset) {
883 if (!mtime_unset) {
867 goto bail;
884 goto bail;
868 }
885 }
869 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
886 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
870 goto bail;
887 goto bail;
871 }
888 }
872 Py_DECREF(mtime_unset);
889 Py_DECREF(mtime_unset);
873 mtime_unset = NULL;
890 mtime_unset = NULL;
874 }
891 }
875 *p++ = state;
892 *p++ = state;
876 putbe32((uint32_t)mode, p);
893 putbe32((uint32_t)mode, p);
877 putbe32((uint32_t)size, p + 4);
894 putbe32((uint32_t)size, p + 4);
878 putbe32((uint32_t)mtime, p + 8);
895 putbe32((uint32_t)mtime, p + 8);
879 t = p + 12;
896 t = p + 12;
880 p += 16;
897 p += 16;
881 len = PyBytes_GET_SIZE(k);
898 len = PyBytes_GET_SIZE(k);
882 memcpy(p, PyBytes_AS_STRING(k), len);
899 memcpy(p, PyBytes_AS_STRING(k), len);
883 p += len;
900 p += len;
884 o = PyDict_GetItem(copymap, k);
901 o = PyDict_GetItem(copymap, k);
885 if (o) {
902 if (o) {
886 *p++ = '\0';
903 *p++ = '\0';
887 l = PyBytes_GET_SIZE(o);
904 l = PyBytes_GET_SIZE(o);
888 memcpy(p, PyBytes_AS_STRING(o), l);
905 memcpy(p, PyBytes_AS_STRING(o), l);
889 p += l;
906 p += l;
890 len += l + 1;
907 len += l + 1;
891 }
908 }
892 putbe32((uint32_t)len, t);
909 putbe32((uint32_t)len, t);
893 }
910 }
894
911
895 pos = p - PyBytes_AS_STRING(packobj);
912 pos = p - PyBytes_AS_STRING(packobj);
896 if (pos != nbytes) {
913 if (pos != nbytes) {
897 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
914 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
898 (long)pos, (long)nbytes);
915 (long)pos, (long)nbytes);
899 goto bail;
916 goto bail;
900 }
917 }
901
918
902 return packobj;
919 return packobj;
903 bail:
920 bail:
904 Py_XDECREF(mtime_unset);
921 Py_XDECREF(mtime_unset);
905 Py_XDECREF(packobj);
922 Py_XDECREF(packobj);
906 Py_XDECREF(v);
923 Py_XDECREF(v);
907 return NULL;
924 return NULL;
908 }
925 }
909
926
910 #define BUMPED_FIX 1
927 #define BUMPED_FIX 1
911 #define USING_SHA_256 2
928 #define USING_SHA_256 2
912 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
929 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
913
930
914 static PyObject *readshas(const char *source, unsigned char num,
931 static PyObject *readshas(const char *source, unsigned char num,
915 Py_ssize_t hashwidth)
932 Py_ssize_t hashwidth)
916 {
933 {
917 int i;
934 int i;
918 PyObject *list = PyTuple_New(num);
935 PyObject *list = PyTuple_New(num);
919 if (list == NULL) {
936 if (list == NULL) {
920 return NULL;
937 return NULL;
921 }
938 }
922 for (i = 0; i < num; i++) {
939 for (i = 0; i < num; i++) {
923 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
940 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
924 if (hash == NULL) {
941 if (hash == NULL) {
925 Py_DECREF(list);
942 Py_DECREF(list);
926 return NULL;
943 return NULL;
927 }
944 }
928 PyTuple_SET_ITEM(list, i, hash);
945 PyTuple_SET_ITEM(list, i, hash);
929 source += hashwidth;
946 source += hashwidth;
930 }
947 }
931 return list;
948 return list;
932 }
949 }
933
950
934 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
951 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
935 uint32_t *msize)
952 uint32_t *msize)
936 {
953 {
937 const char *data = databegin;
954 const char *data = databegin;
938 const char *meta;
955 const char *meta;
939
956
940 double mtime;
957 double mtime;
941 int16_t tz;
958 int16_t tz;
942 uint16_t flags;
959 uint16_t flags;
943 unsigned char nsuccs, nparents, nmetadata;
960 unsigned char nsuccs, nparents, nmetadata;
944 Py_ssize_t hashwidth = 20;
961 Py_ssize_t hashwidth = 20;
945
962
946 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
963 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
947 PyObject *metadata = NULL, *ret = NULL;
964 PyObject *metadata = NULL, *ret = NULL;
948 int i;
965 int i;
949
966
950 if (data + FM1_HEADER_SIZE > dataend) {
967 if (data + FM1_HEADER_SIZE > dataend) {
951 goto overflow;
968 goto overflow;
952 }
969 }
953
970
954 *msize = getbe32(data);
971 *msize = getbe32(data);
955 data += 4;
972 data += 4;
956 mtime = getbefloat64(data);
973 mtime = getbefloat64(data);
957 data += 8;
974 data += 8;
958 tz = getbeint16(data);
975 tz = getbeint16(data);
959 data += 2;
976 data += 2;
960 flags = getbeuint16(data);
977 flags = getbeuint16(data);
961 data += 2;
978 data += 2;
962
979
963 if (flags & USING_SHA_256) {
980 if (flags & USING_SHA_256) {
964 hashwidth = 32;
981 hashwidth = 32;
965 }
982 }
966
983
967 nsuccs = (unsigned char)(*data++);
984 nsuccs = (unsigned char)(*data++);
968 nparents = (unsigned char)(*data++);
985 nparents = (unsigned char)(*data++);
969 nmetadata = (unsigned char)(*data++);
986 nmetadata = (unsigned char)(*data++);
970
987
971 if (databegin + *msize > dataend) {
988 if (databegin + *msize > dataend) {
972 goto overflow;
989 goto overflow;
973 }
990 }
974 dataend = databegin + *msize; /* narrow down to marker size */
991 dataend = databegin + *msize; /* narrow down to marker size */
975
992
976 if (data + hashwidth > dataend) {
993 if (data + hashwidth > dataend) {
977 goto overflow;
994 goto overflow;
978 }
995 }
979 prec = PyBytes_FromStringAndSize(data, hashwidth);
996 prec = PyBytes_FromStringAndSize(data, hashwidth);
980 data += hashwidth;
997 data += hashwidth;
981 if (prec == NULL) {
998 if (prec == NULL) {
982 goto bail;
999 goto bail;
983 }
1000 }
984
1001
985 if (data + nsuccs * hashwidth > dataend) {
1002 if (data + nsuccs * hashwidth > dataend) {
986 goto overflow;
1003 goto overflow;
987 }
1004 }
988 succs = readshas(data, nsuccs, hashwidth);
1005 succs = readshas(data, nsuccs, hashwidth);
989 if (succs == NULL) {
1006 if (succs == NULL) {
990 goto bail;
1007 goto bail;
991 }
1008 }
992 data += nsuccs * hashwidth;
1009 data += nsuccs * hashwidth;
993
1010
994 if (nparents == 1 || nparents == 2) {
1011 if (nparents == 1 || nparents == 2) {
995 if (data + nparents * hashwidth > dataend) {
1012 if (data + nparents * hashwidth > dataend) {
996 goto overflow;
1013 goto overflow;
997 }
1014 }
998 parents = readshas(data, nparents, hashwidth);
1015 parents = readshas(data, nparents, hashwidth);
999 if (parents == NULL) {
1016 if (parents == NULL) {
1000 goto bail;
1017 goto bail;
1001 }
1018 }
1002 data += nparents * hashwidth;
1019 data += nparents * hashwidth;
1003 } else {
1020 } else {
1004 parents = Py_None;
1021 parents = Py_None;
1005 Py_INCREF(parents);
1022 Py_INCREF(parents);
1006 }
1023 }
1007
1024
1008 if (data + 2 * nmetadata > dataend) {
1025 if (data + 2 * nmetadata > dataend) {
1009 goto overflow;
1026 goto overflow;
1010 }
1027 }
1011 meta = data + (2 * nmetadata);
1028 meta = data + (2 * nmetadata);
1012 metadata = PyTuple_New(nmetadata);
1029 metadata = PyTuple_New(nmetadata);
1013 if (metadata == NULL) {
1030 if (metadata == NULL) {
1014 goto bail;
1031 goto bail;
1015 }
1032 }
1016 for (i = 0; i < nmetadata; i++) {
1033 for (i = 0; i < nmetadata; i++) {
1017 PyObject *tmp, *left = NULL, *right = NULL;
1034 PyObject *tmp, *left = NULL, *right = NULL;
1018 Py_ssize_t leftsize = (unsigned char)(*data++);
1035 Py_ssize_t leftsize = (unsigned char)(*data++);
1019 Py_ssize_t rightsize = (unsigned char)(*data++);
1036 Py_ssize_t rightsize = (unsigned char)(*data++);
1020 if (meta + leftsize + rightsize > dataend) {
1037 if (meta + leftsize + rightsize > dataend) {
1021 goto overflow;
1038 goto overflow;
1022 }
1039 }
1023 left = PyBytes_FromStringAndSize(meta, leftsize);
1040 left = PyBytes_FromStringAndSize(meta, leftsize);
1024 meta += leftsize;
1041 meta += leftsize;
1025 right = PyBytes_FromStringAndSize(meta, rightsize);
1042 right = PyBytes_FromStringAndSize(meta, rightsize);
1026 meta += rightsize;
1043 meta += rightsize;
1027 tmp = PyTuple_New(2);
1044 tmp = PyTuple_New(2);
1028 if (!left || !right || !tmp) {
1045 if (!left || !right || !tmp) {
1029 Py_XDECREF(left);
1046 Py_XDECREF(left);
1030 Py_XDECREF(right);
1047 Py_XDECREF(right);
1031 Py_XDECREF(tmp);
1048 Py_XDECREF(tmp);
1032 goto bail;
1049 goto bail;
1033 }
1050 }
1034 PyTuple_SET_ITEM(tmp, 0, left);
1051 PyTuple_SET_ITEM(tmp, 0, left);
1035 PyTuple_SET_ITEM(tmp, 1, right);
1052 PyTuple_SET_ITEM(tmp, 1, right);
1036 PyTuple_SET_ITEM(metadata, i, tmp);
1053 PyTuple_SET_ITEM(metadata, i, tmp);
1037 }
1054 }
1038 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1055 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1039 (int)tz * 60, parents);
1056 (int)tz * 60, parents);
1040 goto bail; /* return successfully */
1057 goto bail; /* return successfully */
1041
1058
1042 overflow:
1059 overflow:
1043 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1060 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1044 bail:
1061 bail:
1045 Py_XDECREF(prec);
1062 Py_XDECREF(prec);
1046 Py_XDECREF(succs);
1063 Py_XDECREF(succs);
1047 Py_XDECREF(metadata);
1064 Py_XDECREF(metadata);
1048 Py_XDECREF(parents);
1065 Py_XDECREF(parents);
1049 return ret;
1066 return ret;
1050 }
1067 }
1051
1068
1052 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1069 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1053 {
1070 {
1054 const char *data, *dataend;
1071 const char *data, *dataend;
1055 Py_ssize_t datalen, offset, stop;
1072 Py_ssize_t datalen, offset, stop;
1056 PyObject *markers = NULL;
1073 PyObject *markers = NULL;
1057
1074
1058 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1075 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1059 &offset, &stop)) {
1076 &offset, &stop)) {
1060 return NULL;
1077 return NULL;
1061 }
1078 }
1062 if (offset < 0) {
1079 if (offset < 0) {
1063 PyErr_SetString(PyExc_ValueError,
1080 PyErr_SetString(PyExc_ValueError,
1064 "invalid negative offset in fm1readmarkers");
1081 "invalid negative offset in fm1readmarkers");
1065 return NULL;
1082 return NULL;
1066 }
1083 }
1067 if (stop > datalen) {
1084 if (stop > datalen) {
1068 PyErr_SetString(
1085 PyErr_SetString(
1069 PyExc_ValueError,
1086 PyExc_ValueError,
1070 "stop longer than data length in fm1readmarkers");
1087 "stop longer than data length in fm1readmarkers");
1071 return NULL;
1088 return NULL;
1072 }
1089 }
1073 dataend = data + datalen;
1090 dataend = data + datalen;
1074 data += offset;
1091 data += offset;
1075 markers = PyList_New(0);
1092 markers = PyList_New(0);
1076 if (!markers) {
1093 if (!markers) {
1077 return NULL;
1094 return NULL;
1078 }
1095 }
1079 while (offset < stop) {
1096 while (offset < stop) {
1080 uint32_t msize;
1097 uint32_t msize;
1081 int error;
1098 int error;
1082 PyObject *record = fm1readmarker(data, dataend, &msize);
1099 PyObject *record = fm1readmarker(data, dataend, &msize);
1083 if (!record) {
1100 if (!record) {
1084 goto bail;
1101 goto bail;
1085 }
1102 }
1086 error = PyList_Append(markers, record);
1103 error = PyList_Append(markers, record);
1087 Py_DECREF(record);
1104 Py_DECREF(record);
1088 if (error) {
1105 if (error) {
1089 goto bail;
1106 goto bail;
1090 }
1107 }
1091 data += msize;
1108 data += msize;
1092 offset += msize;
1109 offset += msize;
1093 }
1110 }
1094 return markers;
1111 return markers;
1095 bail:
1112 bail:
1096 Py_DECREF(markers);
1113 Py_DECREF(markers);
1097 return NULL;
1114 return NULL;
1098 }
1115 }
1099
1116
1100 static char parsers_doc[] = "Efficient content parsing.";
1117 static char parsers_doc[] = "Efficient content parsing.";
1101
1118
1102 PyObject *encodedir(PyObject *self, PyObject *args);
1119 PyObject *encodedir(PyObject *self, PyObject *args);
1103 PyObject *pathencode(PyObject *self, PyObject *args);
1120 PyObject *pathencode(PyObject *self, PyObject *args);
1104 PyObject *lowerencode(PyObject *self, PyObject *args);
1121 PyObject *lowerencode(PyObject *self, PyObject *args);
1105 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1122 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1106
1123
1107 static PyMethodDef methods[] = {
1124 static PyMethodDef methods[] = {
1108 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1125 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1109 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1126 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1110 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1127 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1111 "parse a revlog index\n"},
1128 "parse a revlog index\n"},
1112 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1129 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1113 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1130 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1114 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1131 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1115 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1132 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1116 "construct a dict with an expected size\n"},
1133 "construct a dict with an expected size\n"},
1117 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1134 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1118 "make file foldmap\n"},
1135 "make file foldmap\n"},
1119 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1136 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1120 "escape a UTF-8 byte string to JSON (fast path)\n"},
1137 "escape a UTF-8 byte string to JSON (fast path)\n"},
1121 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1138 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1122 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1139 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1123 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1140 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1124 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1141 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1125 "parse v1 obsolete markers\n"},
1142 "parse v1 obsolete markers\n"},
1126 {NULL, NULL}};
1143 {NULL, NULL}};
1127
1144
1128 void dirs_module_init(PyObject *mod);
1145 void dirs_module_init(PyObject *mod);
1129 void manifest_module_init(PyObject *mod);
1146 void manifest_module_init(PyObject *mod);
1130 void revlog_module_init(PyObject *mod);
1147 void revlog_module_init(PyObject *mod);
1131
1148
1132 static const int version = 20;
1149 static const int version = 20;
1133
1150
1134 static void module_init(PyObject *mod)
1151 static void module_init(PyObject *mod)
1135 {
1152 {
1136 PyModule_AddIntConstant(mod, "version", version);
1153 PyModule_AddIntConstant(mod, "version", version);
1137
1154
1138 /* This module constant has two purposes. First, it lets us unit test
1155 /* This module constant has two purposes. First, it lets us unit test
1139 * the ImportError raised without hard-coding any error text. This
1156 * the ImportError raised without hard-coding any error text. This
1140 * means we can change the text in the future without breaking tests,
1157 * means we can change the text in the future without breaking tests,
1141 * even across changesets without a recompile. Second, its presence
1158 * even across changesets without a recompile. Second, its presence
1142 * can be used to determine whether the version-checking logic is
1159 * can be used to determine whether the version-checking logic is
1143 * present, which also helps in testing across changesets without a
1160 * present, which also helps in testing across changesets without a
1144 * recompile. Note that this means the pure-Python version of parsers
1161 * recompile. Note that this means the pure-Python version of parsers
1145 * should not have this module constant. */
1162 * should not have this module constant. */
1146 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1163 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1147
1164
1148 dirs_module_init(mod);
1165 dirs_module_init(mod);
1149 manifest_module_init(mod);
1166 manifest_module_init(mod);
1150 revlog_module_init(mod);
1167 revlog_module_init(mod);
1151
1168
1152 if (PyType_Ready(&dirstateItemType) < 0) {
1169 if (PyType_Ready(&dirstateItemType) < 0) {
1153 return;
1170 return;
1154 }
1171 }
1155 Py_INCREF(&dirstateItemType);
1172 Py_INCREF(&dirstateItemType);
1156 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1173 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1157 }
1174 }
1158
1175
1159 static int check_python_version(void)
1176 static int check_python_version(void)
1160 {
1177 {
1161 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1178 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1162 long hexversion;
1179 long hexversion;
1163 if (!sys) {
1180 if (!sys) {
1164 return -1;
1181 return -1;
1165 }
1182 }
1166 ver = PyObject_GetAttrString(sys, "hexversion");
1183 ver = PyObject_GetAttrString(sys, "hexversion");
1167 Py_DECREF(sys);
1184 Py_DECREF(sys);
1168 if (!ver) {
1185 if (!ver) {
1169 return -1;
1186 return -1;
1170 }
1187 }
1171 hexversion = PyInt_AsLong(ver);
1188 hexversion = PyInt_AsLong(ver);
1172 Py_DECREF(ver);
1189 Py_DECREF(ver);
1173 /* sys.hexversion is a 32-bit number by default, so the -1 case
1190 /* sys.hexversion is a 32-bit number by default, so the -1 case
1174 * should only occur in unusual circumstances (e.g. if sys.hexversion
1191 * should only occur in unusual circumstances (e.g. if sys.hexversion
1175 * is manually set to an invalid value). */
1192 * is manually set to an invalid value). */
1176 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1193 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1177 PyErr_Format(PyExc_ImportError,
1194 PyErr_Format(PyExc_ImportError,
1178 "%s: The Mercurial extension "
1195 "%s: The Mercurial extension "
1179 "modules were compiled with Python " PY_VERSION
1196 "modules were compiled with Python " PY_VERSION
1180 ", but "
1197 ", but "
1181 "Mercurial is currently using Python with "
1198 "Mercurial is currently using Python with "
1182 "sys.hexversion=%ld: "
1199 "sys.hexversion=%ld: "
1183 "Python %s\n at: %s",
1200 "Python %s\n at: %s",
1184 versionerrortext, hexversion, Py_GetVersion(),
1201 versionerrortext, hexversion, Py_GetVersion(),
1185 Py_GetProgramFullPath());
1202 Py_GetProgramFullPath());
1186 return -1;
1203 return -1;
1187 }
1204 }
1188 return 0;
1205 return 0;
1189 }
1206 }
1190
1207
1191 #ifdef IS_PY3K
1208 #ifdef IS_PY3K
1192 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1209 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1193 parsers_doc, -1, methods};
1210 parsers_doc, -1, methods};
1194
1211
1195 PyMODINIT_FUNC PyInit_parsers(void)
1212 PyMODINIT_FUNC PyInit_parsers(void)
1196 {
1213 {
1197 PyObject *mod;
1214 PyObject *mod;
1198
1215
1199 if (check_python_version() == -1)
1216 if (check_python_version() == -1)
1200 return NULL;
1217 return NULL;
1201 mod = PyModule_Create(&parsers_module);
1218 mod = PyModule_Create(&parsers_module);
1202 module_init(mod);
1219 module_init(mod);
1203 return mod;
1220 return mod;
1204 }
1221 }
1205 #else
1222 #else
1206 PyMODINIT_FUNC initparsers(void)
1223 PyMODINIT_FUNC initparsers(void)
1207 {
1224 {
1208 PyObject *mod;
1225 PyObject *mod;
1209
1226
1210 if (check_python_version() == -1) {
1227 if (check_python_version() == -1) {
1211 return;
1228 return;
1212 }
1229 }
1213 mod = Py_InitModule3("parsers", methods, parsers_doc);
1230 mod = Py_InitModule3("parsers", methods, parsers_doc);
1214 module_init(mod);
1231 module_init(mod);
1215 }
1232 }
1216 #endif
1233 #endif
@@ -1,806 +1,819 b''
1 # parsers.py - Python implementation of parsers.c
1 # parsers.py - Python implementation of parsers.c
2 #
2 #
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11 import zlib
11 import zlib
12
12
13 from ..node import (
13 from ..node import (
14 nullrev,
14 nullrev,
15 sha1nodeconstants,
15 sha1nodeconstants,
16 )
16 )
17 from ..thirdparty import attr
17 from ..thirdparty import attr
18 from .. import (
18 from .. import (
19 error,
19 error,
20 pycompat,
20 pycompat,
21 revlogutils,
21 revlogutils,
22 util,
22 util,
23 )
23 )
24
24
25 from ..revlogutils import nodemap as nodemaputil
25 from ..revlogutils import nodemap as nodemaputil
26 from ..revlogutils import constants as revlog_constants
26 from ..revlogutils import constants as revlog_constants
27
27
28 stringio = pycompat.bytesio
28 stringio = pycompat.bytesio
29
29
30
30
31 _pack = struct.pack
31 _pack = struct.pack
32 _unpack = struct.unpack
32 _unpack = struct.unpack
33 _compress = zlib.compress
33 _compress = zlib.compress
34 _decompress = zlib.decompress
34 _decompress = zlib.decompress
35
35
36
36
37 # a special value used internally for `size` if the file come from the other parent
37 # a special value used internally for `size` if the file come from the other parent
38 FROM_P2 = -2
38 FROM_P2 = -2
39
39
40 # a special value used internally for `size` if the file is modified/merged/added
40 # a special value used internally for `size` if the file is modified/merged/added
41 NONNORMAL = -1
41 NONNORMAL = -1
42
42
43 # a special value used internally for `time` if the time is ambigeous
43 # a special value used internally for `time` if the time is ambigeous
44 AMBIGUOUS_TIME = -1
44 AMBIGUOUS_TIME = -1
45
45
46
46
47 @attr.s(slots=True, init=False)
47 @attr.s(slots=True, init=False)
48 class DirstateItem(object):
48 class DirstateItem(object):
49 """represent a dirstate entry
49 """represent a dirstate entry
50
50
51 It contains:
51 It contains:
52
52
53 - state (one of 'n', 'a', 'r', 'm')
53 - state (one of 'n', 'a', 'r', 'm')
54 - mode,
54 - mode,
55 - size,
55 - size,
56 - mtime,
56 - mtime,
57 """
57 """
58
58
59 _wc_tracked = attr.ib()
59 _wc_tracked = attr.ib()
60 _p1_tracked = attr.ib()
60 _p1_tracked = attr.ib()
61 _p2_tracked = attr.ib()
61 _p2_tracked = attr.ib()
62 # the three item above should probably be combined
62 # the three item above should probably be combined
63 #
63 #
64 # However it is unclear if they properly cover some of the most advanced
64 # However it is unclear if they properly cover some of the most advanced
65 # merge case. So we should probably wait on this to be settled.
65 # merge case. So we should probably wait on this to be settled.
66 _merged = attr.ib()
66 _merged = attr.ib()
67 _clean_p1 = attr.ib()
67 _clean_p1 = attr.ib()
68 _clean_p2 = attr.ib()
68 _clean_p2 = attr.ib()
69 _possibly_dirty = attr.ib()
69 _possibly_dirty = attr.ib()
70 _mode = attr.ib()
70 _mode = attr.ib()
71 _size = attr.ib()
71 _size = attr.ib()
72 _mtime = attr.ib()
72 _mtime = attr.ib()
73
73
74 def __init__(
74 def __init__(
75 self,
75 self,
76 wc_tracked=False,
76 wc_tracked=False,
77 p1_tracked=False,
77 p1_tracked=False,
78 p2_tracked=False,
78 p2_tracked=False,
79 merged=False,
79 merged=False,
80 clean_p1=False,
80 clean_p1=False,
81 clean_p2=False,
81 clean_p2=False,
82 possibly_dirty=False,
82 possibly_dirty=False,
83 parentfiledata=None,
83 parentfiledata=None,
84 ):
84 ):
85 if merged and (clean_p1 or clean_p2):
85 if merged and (clean_p1 or clean_p2):
86 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
86 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
87 raise error.ProgrammingError(msg)
87 raise error.ProgrammingError(msg)
88
88
89 self._wc_tracked = wc_tracked
89 self._wc_tracked = wc_tracked
90 self._p1_tracked = p1_tracked
90 self._p1_tracked = p1_tracked
91 self._p2_tracked = p2_tracked
91 self._p2_tracked = p2_tracked
92 self._merged = merged
92 self._merged = merged
93 self._clean_p1 = clean_p1
93 self._clean_p1 = clean_p1
94 self._clean_p2 = clean_p2
94 self._clean_p2 = clean_p2
95 self._possibly_dirty = possibly_dirty
95 self._possibly_dirty = possibly_dirty
96 if parentfiledata is None:
96 if parentfiledata is None:
97 self._mode = None
97 self._mode = None
98 self._size = None
98 self._size = None
99 self._mtime = None
99 self._mtime = None
100 else:
100 else:
101 self._mode = parentfiledata[0]
101 self._mode = parentfiledata[0]
102 self._size = parentfiledata[1]
102 self._size = parentfiledata[1]
103 self._mtime = parentfiledata[2]
103 self._mtime = parentfiledata[2]
104
104
105 @classmethod
105 @classmethod
106 def new_added(cls):
106 def new_added(cls):
107 """constructor to help legacy API to build a new "added" item
107 """constructor to help legacy API to build a new "added" item
108
108
109 Should eventually be removed
109 Should eventually be removed
110 """
110 """
111 instance = cls()
111 instance = cls()
112 instance._wc_tracked = True
112 instance._wc_tracked = True
113 instance._p1_tracked = False
113 instance._p1_tracked = False
114 instance._p2_tracked = False
114 instance._p2_tracked = False
115 return instance
115 return instance
116
116
117 @classmethod
117 @classmethod
118 def new_merged(cls):
118 def new_merged(cls):
119 """constructor to help legacy API to build a new "merged" item
119 """constructor to help legacy API to build a new "merged" item
120
120
121 Should eventually be removed
121 Should eventually be removed
122 """
122 """
123 instance = cls()
123 instance = cls()
124 instance._wc_tracked = True
124 instance._wc_tracked = True
125 instance._p1_tracked = True # might not be True because of rename ?
125 instance._p1_tracked = True # might not be True because of rename ?
126 instance._p2_tracked = True # might not be True because of rename ?
126 instance._p2_tracked = True # might not be True because of rename ?
127 instance._merged = True
127 instance._merged = True
128 return instance
128 return instance
129
129
130 @classmethod
130 @classmethod
131 def new_from_p2(cls):
131 def new_from_p2(cls):
132 """constructor to help legacy API to build a new "from_p2" item
132 """constructor to help legacy API to build a new "from_p2" item
133
133
134 Should eventually be removed
134 Should eventually be removed
135 """
135 """
136 instance = cls()
136 instance = cls()
137 instance._wc_tracked = True
137 instance._wc_tracked = True
138 instance._p1_tracked = False # might actually be True
138 instance._p1_tracked = False # might actually be True
139 instance._p2_tracked = True
139 instance._p2_tracked = True
140 instance._clean_p2 = True
140 instance._clean_p2 = True
141 return instance
141 return instance
142
142
143 @classmethod
143 @classmethod
144 def new_possibly_dirty(cls):
144 def new_possibly_dirty(cls):
145 """constructor to help legacy API to build a new "possibly_dirty" item
145 """constructor to help legacy API to build a new "possibly_dirty" item
146
146
147 Should eventually be removed
147 Should eventually be removed
148 """
148 """
149 instance = cls()
149 instance = cls()
150 instance._wc_tracked = True
150 instance._wc_tracked = True
151 instance._p1_tracked = True
151 instance._p1_tracked = True
152 instance._possibly_dirty = True
152 instance._possibly_dirty = True
153 return instance
153 return instance
154
154
155 @classmethod
155 @classmethod
156 def new_normal(cls, mode, size, mtime):
156 def new_normal(cls, mode, size, mtime):
157 """constructor to help legacy API to build a new "normal" item
157 """constructor to help legacy API to build a new "normal" item
158
158
159 Should eventually be removed
159 Should eventually be removed
160 """
160 """
161 assert size != FROM_P2
161 assert size != FROM_P2
162 assert size != NONNORMAL
162 assert size != NONNORMAL
163 instance = cls()
163 instance = cls()
164 instance._wc_tracked = True
164 instance._wc_tracked = True
165 instance._p1_tracked = True
165 instance._p1_tracked = True
166 instance._mode = mode
166 instance._mode = mode
167 instance._size = size
167 instance._size = size
168 instance._mtime = mtime
168 instance._mtime = mtime
169 return instance
169 return instance
170
170
171 @classmethod
171 @classmethod
172 def from_v1_data(cls, state, mode, size, mtime):
172 def from_v1_data(cls, state, mode, size, mtime):
173 """Build a new DirstateItem object from V1 data
173 """Build a new DirstateItem object from V1 data
174
174
175 Since the dirstate-v1 format is frozen, the signature of this function
175 Since the dirstate-v1 format is frozen, the signature of this function
176 is not expected to change, unlike the __init__ one.
176 is not expected to change, unlike the __init__ one.
177 """
177 """
178 if state == b'm':
178 if state == b'm':
179 return cls.new_merged()
179 return cls.new_merged()
180 elif state == b'a':
180 elif state == b'a':
181 return cls.new_added()
181 return cls.new_added()
182 elif state == b'r':
182 elif state == b'r':
183 instance = cls()
183 instance = cls()
184 instance._wc_tracked = False
184 instance._wc_tracked = False
185 if size == NONNORMAL:
185 if size == NONNORMAL:
186 instance._merged = True
186 instance._merged = True
187 instance._p1_tracked = (
187 instance._p1_tracked = (
188 True # might not be True because of rename ?
188 True # might not be True because of rename ?
189 )
189 )
190 instance._p2_tracked = (
190 instance._p2_tracked = (
191 True # might not be True because of rename ?
191 True # might not be True because of rename ?
192 )
192 )
193 elif size == FROM_P2:
193 elif size == FROM_P2:
194 instance._clean_p2 = True
194 instance._clean_p2 = True
195 instance._p1_tracked = (
195 instance._p1_tracked = (
196 False # We actually don't know (file history)
196 False # We actually don't know (file history)
197 )
197 )
198 instance._p2_tracked = True
198 instance._p2_tracked = True
199 else:
199 else:
200 instance._p1_tracked = True
200 instance._p1_tracked = True
201 return instance
201 return instance
202 elif state == b'n':
202 elif state == b'n':
203 if size == FROM_P2:
203 if size == FROM_P2:
204 return cls.new_from_p2()
204 return cls.new_from_p2()
205 elif size == NONNORMAL:
205 elif size == NONNORMAL:
206 return cls.new_possibly_dirty()
206 return cls.new_possibly_dirty()
207 elif mtime == AMBIGUOUS_TIME:
207 elif mtime == AMBIGUOUS_TIME:
208 instance = cls.new_normal(mode, size, 42)
208 instance = cls.new_normal(mode, size, 42)
209 instance._mtime = None
209 instance._mtime = None
210 instance._possibly_dirty = True
210 instance._possibly_dirty = True
211 return instance
211 return instance
212 else:
212 else:
213 return cls.new_normal(mode, size, mtime)
213 return cls.new_normal(mode, size, mtime)
214 else:
214 else:
215 raise RuntimeError(b'unknown state: %s' % state)
215 raise RuntimeError(b'unknown state: %s' % state)
216
216
217 def set_possibly_dirty(self):
217 def set_possibly_dirty(self):
218 """Mark a file as "possibly dirty"
218 """Mark a file as "possibly dirty"
219
219
220 This means the next status call will have to actually check its content
220 This means the next status call will have to actually check its content
221 to make sure it is correct.
221 to make sure it is correct.
222 """
222 """
223 self._possibly_dirty = True
223 self._possibly_dirty = True
224
224
225 def set_clean(self, mode, size, mtime):
225 def set_clean(self, mode, size, mtime):
226 """mark a file as "clean" cancelling potential "possibly dirty call"
226 """mark a file as "clean" cancelling potential "possibly dirty call"
227
227
228 Note: this function is a descendant of `dirstate.normal` and is
228 Note: this function is a descendant of `dirstate.normal` and is
229 currently expected to be call on "normal" entry only. There are not
229 currently expected to be call on "normal" entry only. There are not
230 reason for this to not change in the future as long as the ccode is
230 reason for this to not change in the future as long as the ccode is
231 updated to preserve the proper state of the non-normal files.
231 updated to preserve the proper state of the non-normal files.
232 """
232 """
233 self._wc_tracked = True
233 self._wc_tracked = True
234 self._p1_tracked = True
234 self._p1_tracked = True
235 self._p2_tracked = False # this might be wrong
235 self._p2_tracked = False # this might be wrong
236 self._merged = False
236 self._merged = False
237 self._clean_p2 = False
237 self._clean_p2 = False
238 self._possibly_dirty = False
238 self._possibly_dirty = False
239 self._mode = mode
239 self._mode = mode
240 self._size = size
240 self._size = size
241 self._mtime = mtime
241 self._mtime = mtime
242
242
243 def set_tracked(self):
243 def set_tracked(self):
244 """mark a file as tracked in the working copy
244 """mark a file as tracked in the working copy
245
245
246 This will ultimately be called by command like `hg add`.
246 This will ultimately be called by command like `hg add`.
247 """
247 """
248 self._wc_tracked = True
248 self._wc_tracked = True
249 # `set_tracked` is replacing various `normallookup` call. So we set
249 # `set_tracked` is replacing various `normallookup` call. So we set
250 # "possibly dirty" to stay on the safe side.
250 # "possibly dirty" to stay on the safe side.
251 #
251 #
252 # Consider dropping this in the future in favor of something less broad.
252 # Consider dropping this in the future in favor of something less broad.
253 self._possibly_dirty = True
253 self._possibly_dirty = True
254
254
255 def set_untracked(self):
255 def set_untracked(self):
256 """mark a file as untracked in the working copy
256 """mark a file as untracked in the working copy
257
257
258 This will ultimately be called by command like `hg remove`.
258 This will ultimately be called by command like `hg remove`.
259 """
259 """
260 # backup the previous state (useful for merge)
260 # backup the previous state (useful for merge)
261 self._wc_tracked = False
261 self._wc_tracked = False
262 self._mode = None
262 self._mode = None
263 self._size = None
263 self._size = None
264 self._mtime = None
264 self._mtime = None
265
265
266 def drop_merge_data(self):
266 def drop_merge_data(self):
267 """remove all "merge-only" from a DirstateItem
267 """remove all "merge-only" from a DirstateItem
268
268
269 This is to be call by the dirstatemap code when the second parent is dropped
269 This is to be call by the dirstatemap code when the second parent is dropped
270 """
270 """
271 if not (self.merged or self.from_p2):
271 if not (self.merged or self.from_p2):
272 return
272 return
273 self._p1_tracked = self.merged # why is this not already properly set ?
273 self._p1_tracked = self.merged # why is this not already properly set ?
274
274
275 self._merged = False
275 self._merged = False
276 self._clean_p1 = False
276 self._clean_p1 = False
277 self._clean_p2 = False
277 self._clean_p2 = False
278 self._p2_tracked = False
278 self._p2_tracked = False
279 self._possibly_dirty = True
279 self._possibly_dirty = True
280 self._mode = None
280 self._mode = None
281 self._size = None
281 self._size = None
282 self._mtime = None
282 self._mtime = None
283
283
284 @property
284 @property
285 def mode(self):
285 def mode(self):
286 return self.v1_mode()
286 return self.v1_mode()
287
287
288 @property
288 @property
289 def size(self):
289 def size(self):
290 return self.v1_size()
290 return self.v1_size()
291
291
292 @property
292 @property
293 def mtime(self):
293 def mtime(self):
294 return self.v1_mtime()
294 return self.v1_mtime()
295
295
296 @property
296 @property
297 def state(self):
297 def state(self):
298 """
298 """
299 States are:
299 States are:
300 n normal
300 n normal
301 m needs merging
301 m needs merging
302 r marked for removal
302 r marked for removal
303 a marked for addition
303 a marked for addition
304
304
305 XXX This "state" is a bit obscure and mostly a direct expression of the
305 XXX This "state" is a bit obscure and mostly a direct expression of the
306 dirstatev1 format. It would make sense to ultimately deprecate it in
306 dirstatev1 format. It would make sense to ultimately deprecate it in
307 favor of the more "semantic" attributes.
307 favor of the more "semantic" attributes.
308 """
308 """
309 return self.v1_state()
309 return self.v1_state()
310
310
311 @property
311 @property
312 def tracked(self):
312 def tracked(self):
313 """True is the file is tracked in the working copy"""
313 """True is the file is tracked in the working copy"""
314 return self._wc_tracked
314 return self._wc_tracked
315
315
316 @property
316 @property
317 def added(self):
317 def added(self):
318 """True if the file has been added"""
318 """True if the file has been added"""
319 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked)
319 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked)
320
320
321 @property
321 @property
322 def maybe_clean(self):
323 """True if the file has a chance to be in the "clean" state"""
324 if not self._wc_tracked:
325 return False
326 elif self.added:
327 return False
328 elif self._merged:
329 return False
330 elif self._clean_p2:
331 return False
332 return True
333
334 @property
322 def merged(self):
335 def merged(self):
323 """True if the file has been merged
336 """True if the file has been merged
324
337
325 Should only be set if a merge is in progress in the dirstate
338 Should only be set if a merge is in progress in the dirstate
326 """
339 """
327 return self._wc_tracked and self._merged
340 return self._wc_tracked and self._merged
328
341
329 @property
342 @property
330 def from_p2(self):
343 def from_p2(self):
331 """True if the file have been fetched from p2 during the current merge
344 """True if the file have been fetched from p2 during the current merge
332
345
333 This is only True is the file is currently tracked.
346 This is only True is the file is currently tracked.
334
347
335 Should only be set if a merge is in progress in the dirstate
348 Should only be set if a merge is in progress in the dirstate
336 """
349 """
337 if not self._wc_tracked:
350 if not self._wc_tracked:
338 return False
351 return False
339 return self._clean_p2
352 return self._clean_p2
340
353
341 @property
354 @property
342 def removed(self):
355 def removed(self):
343 """True if the file has been removed"""
356 """True if the file has been removed"""
344 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked)
357 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked)
345
358
346 def v1_state(self):
359 def v1_state(self):
347 """return a "state" suitable for v1 serialization"""
360 """return a "state" suitable for v1 serialization"""
348 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
361 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
349 # the object has no state to record, this is -currently-
362 # the object has no state to record, this is -currently-
350 # unsupported
363 # unsupported
351 raise RuntimeError('untracked item')
364 raise RuntimeError('untracked item')
352 elif self.removed:
365 elif self.removed:
353 return b'r'
366 return b'r'
354 elif self.merged:
367 elif self.merged:
355 return b'm'
368 return b'm'
356 elif self.added:
369 elif self.added:
357 return b'a'
370 return b'a'
358 else:
371 else:
359 return b'n'
372 return b'n'
360
373
361 def v1_mode(self):
374 def v1_mode(self):
362 """return a "mode" suitable for v1 serialization"""
375 """return a "mode" suitable for v1 serialization"""
363 return self._mode if self._mode is not None else 0
376 return self._mode if self._mode is not None else 0
364
377
365 def v1_size(self):
378 def v1_size(self):
366 """return a "size" suitable for v1 serialization"""
379 """return a "size" suitable for v1 serialization"""
367 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
380 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
368 # the object has no state to record, this is -currently-
381 # the object has no state to record, this is -currently-
369 # unsupported
382 # unsupported
370 raise RuntimeError('untracked item')
383 raise RuntimeError('untracked item')
371 elif self.removed and self._merged:
384 elif self.removed and self._merged:
372 return NONNORMAL
385 return NONNORMAL
373 elif self.removed and self._clean_p2:
386 elif self.removed and self._clean_p2:
374 return FROM_P2
387 return FROM_P2
375 elif self.removed:
388 elif self.removed:
376 return 0
389 return 0
377 elif self.merged:
390 elif self.merged:
378 return FROM_P2
391 return FROM_P2
379 elif self.added:
392 elif self.added:
380 return NONNORMAL
393 return NONNORMAL
381 elif self.from_p2:
394 elif self.from_p2:
382 return FROM_P2
395 return FROM_P2
383 elif self._possibly_dirty:
396 elif self._possibly_dirty:
384 return self._size if self._size is not None else NONNORMAL
397 return self._size if self._size is not None else NONNORMAL
385 else:
398 else:
386 return self._size
399 return self._size
387
400
388 def v1_mtime(self):
401 def v1_mtime(self):
389 """return a "mtime" suitable for v1 serialization"""
402 """return a "mtime" suitable for v1 serialization"""
390 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
403 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
391 # the object has no state to record, this is -currently-
404 # the object has no state to record, this is -currently-
392 # unsupported
405 # unsupported
393 raise RuntimeError('untracked item')
406 raise RuntimeError('untracked item')
394 elif self.removed:
407 elif self.removed:
395 return 0
408 return 0
396 elif self._possibly_dirty:
409 elif self._possibly_dirty:
397 return AMBIGUOUS_TIME
410 return AMBIGUOUS_TIME
398 elif self.merged:
411 elif self.merged:
399 return AMBIGUOUS_TIME
412 return AMBIGUOUS_TIME
400 elif self.added:
413 elif self.added:
401 return AMBIGUOUS_TIME
414 return AMBIGUOUS_TIME
402 elif self.from_p2:
415 elif self.from_p2:
403 return AMBIGUOUS_TIME
416 return AMBIGUOUS_TIME
404 else:
417 else:
405 return self._mtime if self._mtime is not None else 0
418 return self._mtime if self._mtime is not None else 0
406
419
407 def need_delay(self, now):
420 def need_delay(self, now):
408 """True if the stored mtime would be ambiguous with the current time"""
421 """True if the stored mtime would be ambiguous with the current time"""
409 return self.v1_state() == b'n' and self.v1_mtime() == now
422 return self.v1_state() == b'n' and self.v1_mtime() == now
410
423
411
424
412 def gettype(q):
425 def gettype(q):
413 return int(q & 0xFFFF)
426 return int(q & 0xFFFF)
414
427
415
428
416 class BaseIndexObject(object):
429 class BaseIndexObject(object):
417 # Can I be passed to an algorithme implemented in Rust ?
430 # Can I be passed to an algorithme implemented in Rust ?
418 rust_ext_compat = 0
431 rust_ext_compat = 0
419 # Format of an index entry according to Python's `struct` language
432 # Format of an index entry according to Python's `struct` language
420 index_format = revlog_constants.INDEX_ENTRY_V1
433 index_format = revlog_constants.INDEX_ENTRY_V1
421 # Size of a C unsigned long long int, platform independent
434 # Size of a C unsigned long long int, platform independent
422 big_int_size = struct.calcsize(b'>Q')
435 big_int_size = struct.calcsize(b'>Q')
423 # Size of a C long int, platform independent
436 # Size of a C long int, platform independent
424 int_size = struct.calcsize(b'>i')
437 int_size = struct.calcsize(b'>i')
425 # An empty index entry, used as a default value to be overridden, or nullrev
438 # An empty index entry, used as a default value to be overridden, or nullrev
426 null_item = (
439 null_item = (
427 0,
440 0,
428 0,
441 0,
429 0,
442 0,
430 -1,
443 -1,
431 -1,
444 -1,
432 -1,
445 -1,
433 -1,
446 -1,
434 sha1nodeconstants.nullid,
447 sha1nodeconstants.nullid,
435 0,
448 0,
436 0,
449 0,
437 revlog_constants.COMP_MODE_INLINE,
450 revlog_constants.COMP_MODE_INLINE,
438 revlog_constants.COMP_MODE_INLINE,
451 revlog_constants.COMP_MODE_INLINE,
439 )
452 )
440
453
441 @util.propertycache
454 @util.propertycache
442 def entry_size(self):
455 def entry_size(self):
443 return self.index_format.size
456 return self.index_format.size
444
457
445 @property
458 @property
446 def nodemap(self):
459 def nodemap(self):
447 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
460 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
448 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
461 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
449 return self._nodemap
462 return self._nodemap
450
463
451 @util.propertycache
464 @util.propertycache
452 def _nodemap(self):
465 def _nodemap(self):
453 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
466 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
454 for r in range(0, len(self)):
467 for r in range(0, len(self)):
455 n = self[r][7]
468 n = self[r][7]
456 nodemap[n] = r
469 nodemap[n] = r
457 return nodemap
470 return nodemap
458
471
459 def has_node(self, node):
472 def has_node(self, node):
460 """return True if the node exist in the index"""
473 """return True if the node exist in the index"""
461 return node in self._nodemap
474 return node in self._nodemap
462
475
463 def rev(self, node):
476 def rev(self, node):
464 """return a revision for a node
477 """return a revision for a node
465
478
466 If the node is unknown, raise a RevlogError"""
479 If the node is unknown, raise a RevlogError"""
467 return self._nodemap[node]
480 return self._nodemap[node]
468
481
469 def get_rev(self, node):
482 def get_rev(self, node):
470 """return a revision for a node
483 """return a revision for a node
471
484
472 If the node is unknown, return None"""
485 If the node is unknown, return None"""
473 return self._nodemap.get(node)
486 return self._nodemap.get(node)
474
487
475 def _stripnodes(self, start):
488 def _stripnodes(self, start):
476 if '_nodemap' in vars(self):
489 if '_nodemap' in vars(self):
477 for r in range(start, len(self)):
490 for r in range(start, len(self)):
478 n = self[r][7]
491 n = self[r][7]
479 del self._nodemap[n]
492 del self._nodemap[n]
480
493
481 def clearcaches(self):
494 def clearcaches(self):
482 self.__dict__.pop('_nodemap', None)
495 self.__dict__.pop('_nodemap', None)
483
496
484 def __len__(self):
497 def __len__(self):
485 return self._lgt + len(self._extra)
498 return self._lgt + len(self._extra)
486
499
487 def append(self, tup):
500 def append(self, tup):
488 if '_nodemap' in vars(self):
501 if '_nodemap' in vars(self):
489 self._nodemap[tup[7]] = len(self)
502 self._nodemap[tup[7]] = len(self)
490 data = self._pack_entry(len(self), tup)
503 data = self._pack_entry(len(self), tup)
491 self._extra.append(data)
504 self._extra.append(data)
492
505
493 def _pack_entry(self, rev, entry):
506 def _pack_entry(self, rev, entry):
494 assert entry[8] == 0
507 assert entry[8] == 0
495 assert entry[9] == 0
508 assert entry[9] == 0
496 return self.index_format.pack(*entry[:8])
509 return self.index_format.pack(*entry[:8])
497
510
498 def _check_index(self, i):
511 def _check_index(self, i):
499 if not isinstance(i, int):
512 if not isinstance(i, int):
500 raise TypeError(b"expecting int indexes")
513 raise TypeError(b"expecting int indexes")
501 if i < 0 or i >= len(self):
514 if i < 0 or i >= len(self):
502 raise IndexError
515 raise IndexError
503
516
504 def __getitem__(self, i):
517 def __getitem__(self, i):
505 if i == -1:
518 if i == -1:
506 return self.null_item
519 return self.null_item
507 self._check_index(i)
520 self._check_index(i)
508 if i >= self._lgt:
521 if i >= self._lgt:
509 data = self._extra[i - self._lgt]
522 data = self._extra[i - self._lgt]
510 else:
523 else:
511 index = self._calculate_index(i)
524 index = self._calculate_index(i)
512 data = self._data[index : index + self.entry_size]
525 data = self._data[index : index + self.entry_size]
513 r = self._unpack_entry(i, data)
526 r = self._unpack_entry(i, data)
514 if self._lgt and i == 0:
527 if self._lgt and i == 0:
515 offset = revlogutils.offset_type(0, gettype(r[0]))
528 offset = revlogutils.offset_type(0, gettype(r[0]))
516 r = (offset,) + r[1:]
529 r = (offset,) + r[1:]
517 return r
530 return r
518
531
519 def _unpack_entry(self, rev, data):
532 def _unpack_entry(self, rev, data):
520 r = self.index_format.unpack(data)
533 r = self.index_format.unpack(data)
521 r = r + (
534 r = r + (
522 0,
535 0,
523 0,
536 0,
524 revlog_constants.COMP_MODE_INLINE,
537 revlog_constants.COMP_MODE_INLINE,
525 revlog_constants.COMP_MODE_INLINE,
538 revlog_constants.COMP_MODE_INLINE,
526 )
539 )
527 return r
540 return r
528
541
529 def pack_header(self, header):
542 def pack_header(self, header):
530 """pack header information as binary"""
543 """pack header information as binary"""
531 v_fmt = revlog_constants.INDEX_HEADER
544 v_fmt = revlog_constants.INDEX_HEADER
532 return v_fmt.pack(header)
545 return v_fmt.pack(header)
533
546
534 def entry_binary(self, rev):
547 def entry_binary(self, rev):
535 """return the raw binary string representing a revision"""
548 """return the raw binary string representing a revision"""
536 entry = self[rev]
549 entry = self[rev]
537 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
550 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
538 if rev == 0:
551 if rev == 0:
539 p = p[revlog_constants.INDEX_HEADER.size :]
552 p = p[revlog_constants.INDEX_HEADER.size :]
540 return p
553 return p
541
554
542
555
543 class IndexObject(BaseIndexObject):
556 class IndexObject(BaseIndexObject):
544 def __init__(self, data):
557 def __init__(self, data):
545 assert len(data) % self.entry_size == 0, (
558 assert len(data) % self.entry_size == 0, (
546 len(data),
559 len(data),
547 self.entry_size,
560 self.entry_size,
548 len(data) % self.entry_size,
561 len(data) % self.entry_size,
549 )
562 )
550 self._data = data
563 self._data = data
551 self._lgt = len(data) // self.entry_size
564 self._lgt = len(data) // self.entry_size
552 self._extra = []
565 self._extra = []
553
566
554 def _calculate_index(self, i):
567 def _calculate_index(self, i):
555 return i * self.entry_size
568 return i * self.entry_size
556
569
557 def __delitem__(self, i):
570 def __delitem__(self, i):
558 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
571 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
559 raise ValueError(b"deleting slices only supports a:-1 with step 1")
572 raise ValueError(b"deleting slices only supports a:-1 with step 1")
560 i = i.start
573 i = i.start
561 self._check_index(i)
574 self._check_index(i)
562 self._stripnodes(i)
575 self._stripnodes(i)
563 if i < self._lgt:
576 if i < self._lgt:
564 self._data = self._data[: i * self.entry_size]
577 self._data = self._data[: i * self.entry_size]
565 self._lgt = i
578 self._lgt = i
566 self._extra = []
579 self._extra = []
567 else:
580 else:
568 self._extra = self._extra[: i - self._lgt]
581 self._extra = self._extra[: i - self._lgt]
569
582
570
583
571 class PersistentNodeMapIndexObject(IndexObject):
584 class PersistentNodeMapIndexObject(IndexObject):
572 """a Debug oriented class to test persistent nodemap
585 """a Debug oriented class to test persistent nodemap
573
586
574 We need a simple python object to test API and higher level behavior. See
587 We need a simple python object to test API and higher level behavior. See
575 the Rust implementation for more serious usage. This should be used only
588 the Rust implementation for more serious usage. This should be used only
576 through the dedicated `devel.persistent-nodemap` config.
589 through the dedicated `devel.persistent-nodemap` config.
577 """
590 """
578
591
579 def nodemap_data_all(self):
592 def nodemap_data_all(self):
580 """Return bytes containing a full serialization of a nodemap
593 """Return bytes containing a full serialization of a nodemap
581
594
582 The nodemap should be valid for the full set of revisions in the
595 The nodemap should be valid for the full set of revisions in the
583 index."""
596 index."""
584 return nodemaputil.persistent_data(self)
597 return nodemaputil.persistent_data(self)
585
598
586 def nodemap_data_incremental(self):
599 def nodemap_data_incremental(self):
587 """Return bytes containing a incremental update to persistent nodemap
600 """Return bytes containing a incremental update to persistent nodemap
588
601
589 This containst the data for an append-only update of the data provided
602 This containst the data for an append-only update of the data provided
590 in the last call to `update_nodemap_data`.
603 in the last call to `update_nodemap_data`.
591 """
604 """
592 if self._nm_root is None:
605 if self._nm_root is None:
593 return None
606 return None
594 docket = self._nm_docket
607 docket = self._nm_docket
595 changed, data = nodemaputil.update_persistent_data(
608 changed, data = nodemaputil.update_persistent_data(
596 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
609 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
597 )
610 )
598
611
599 self._nm_root = self._nm_max_idx = self._nm_docket = None
612 self._nm_root = self._nm_max_idx = self._nm_docket = None
600 return docket, changed, data
613 return docket, changed, data
601
614
602 def update_nodemap_data(self, docket, nm_data):
615 def update_nodemap_data(self, docket, nm_data):
603 """provide full block of persisted binary data for a nodemap
616 """provide full block of persisted binary data for a nodemap
604
617
605 The data are expected to come from disk. See `nodemap_data_all` for a
618 The data are expected to come from disk. See `nodemap_data_all` for a
606 produceur of such data."""
619 produceur of such data."""
607 if nm_data is not None:
620 if nm_data is not None:
608 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
621 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
609 if self._nm_root:
622 if self._nm_root:
610 self._nm_docket = docket
623 self._nm_docket = docket
611 else:
624 else:
612 self._nm_root = self._nm_max_idx = self._nm_docket = None
625 self._nm_root = self._nm_max_idx = self._nm_docket = None
613
626
614
627
615 class InlinedIndexObject(BaseIndexObject):
628 class InlinedIndexObject(BaseIndexObject):
616 def __init__(self, data, inline=0):
629 def __init__(self, data, inline=0):
617 self._data = data
630 self._data = data
618 self._lgt = self._inline_scan(None)
631 self._lgt = self._inline_scan(None)
619 self._inline_scan(self._lgt)
632 self._inline_scan(self._lgt)
620 self._extra = []
633 self._extra = []
621
634
622 def _inline_scan(self, lgt):
635 def _inline_scan(self, lgt):
623 off = 0
636 off = 0
624 if lgt is not None:
637 if lgt is not None:
625 self._offsets = [0] * lgt
638 self._offsets = [0] * lgt
626 count = 0
639 count = 0
627 while off <= len(self._data) - self.entry_size:
640 while off <= len(self._data) - self.entry_size:
628 start = off + self.big_int_size
641 start = off + self.big_int_size
629 (s,) = struct.unpack(
642 (s,) = struct.unpack(
630 b'>i',
643 b'>i',
631 self._data[start : start + self.int_size],
644 self._data[start : start + self.int_size],
632 )
645 )
633 if lgt is not None:
646 if lgt is not None:
634 self._offsets[count] = off
647 self._offsets[count] = off
635 count += 1
648 count += 1
636 off += self.entry_size + s
649 off += self.entry_size + s
637 if off != len(self._data):
650 if off != len(self._data):
638 raise ValueError(b"corrupted data")
651 raise ValueError(b"corrupted data")
639 return count
652 return count
640
653
641 def __delitem__(self, i):
654 def __delitem__(self, i):
642 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
655 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
643 raise ValueError(b"deleting slices only supports a:-1 with step 1")
656 raise ValueError(b"deleting slices only supports a:-1 with step 1")
644 i = i.start
657 i = i.start
645 self._check_index(i)
658 self._check_index(i)
646 self._stripnodes(i)
659 self._stripnodes(i)
647 if i < self._lgt:
660 if i < self._lgt:
648 self._offsets = self._offsets[:i]
661 self._offsets = self._offsets[:i]
649 self._lgt = i
662 self._lgt = i
650 self._extra = []
663 self._extra = []
651 else:
664 else:
652 self._extra = self._extra[: i - self._lgt]
665 self._extra = self._extra[: i - self._lgt]
653
666
654 def _calculate_index(self, i):
667 def _calculate_index(self, i):
655 return self._offsets[i]
668 return self._offsets[i]
656
669
657
670
658 def parse_index2(data, inline, revlogv2=False):
671 def parse_index2(data, inline, revlogv2=False):
659 if not inline:
672 if not inline:
660 cls = IndexObject2 if revlogv2 else IndexObject
673 cls = IndexObject2 if revlogv2 else IndexObject
661 return cls(data), None
674 return cls(data), None
662 cls = InlinedIndexObject
675 cls = InlinedIndexObject
663 return cls(data, inline), (0, data)
676 return cls(data, inline), (0, data)
664
677
665
678
666 def parse_index_cl_v2(data):
679 def parse_index_cl_v2(data):
667 return IndexChangelogV2(data), None
680 return IndexChangelogV2(data), None
668
681
669
682
670 class IndexObject2(IndexObject):
683 class IndexObject2(IndexObject):
671 index_format = revlog_constants.INDEX_ENTRY_V2
684 index_format = revlog_constants.INDEX_ENTRY_V2
672
685
673 def replace_sidedata_info(
686 def replace_sidedata_info(
674 self,
687 self,
675 rev,
688 rev,
676 sidedata_offset,
689 sidedata_offset,
677 sidedata_length,
690 sidedata_length,
678 offset_flags,
691 offset_flags,
679 compression_mode,
692 compression_mode,
680 ):
693 ):
681 """
694 """
682 Replace an existing index entry's sidedata offset and length with new
695 Replace an existing index entry's sidedata offset and length with new
683 ones.
696 ones.
684 This cannot be used outside of the context of sidedata rewriting,
697 This cannot be used outside of the context of sidedata rewriting,
685 inside the transaction that creates the revision `rev`.
698 inside the transaction that creates the revision `rev`.
686 """
699 """
687 if rev < 0:
700 if rev < 0:
688 raise KeyError
701 raise KeyError
689 self._check_index(rev)
702 self._check_index(rev)
690 if rev < self._lgt:
703 if rev < self._lgt:
691 msg = b"cannot rewrite entries outside of this transaction"
704 msg = b"cannot rewrite entries outside of this transaction"
692 raise KeyError(msg)
705 raise KeyError(msg)
693 else:
706 else:
694 entry = list(self[rev])
707 entry = list(self[rev])
695 entry[0] = offset_flags
708 entry[0] = offset_flags
696 entry[8] = sidedata_offset
709 entry[8] = sidedata_offset
697 entry[9] = sidedata_length
710 entry[9] = sidedata_length
698 entry[11] = compression_mode
711 entry[11] = compression_mode
699 entry = tuple(entry)
712 entry = tuple(entry)
700 new = self._pack_entry(rev, entry)
713 new = self._pack_entry(rev, entry)
701 self._extra[rev - self._lgt] = new
714 self._extra[rev - self._lgt] = new
702
715
703 def _unpack_entry(self, rev, data):
716 def _unpack_entry(self, rev, data):
704 data = self.index_format.unpack(data)
717 data = self.index_format.unpack(data)
705 entry = data[:10]
718 entry = data[:10]
706 data_comp = data[10] & 3
719 data_comp = data[10] & 3
707 sidedata_comp = (data[10] & (3 << 2)) >> 2
720 sidedata_comp = (data[10] & (3 << 2)) >> 2
708 return entry + (data_comp, sidedata_comp)
721 return entry + (data_comp, sidedata_comp)
709
722
710 def _pack_entry(self, rev, entry):
723 def _pack_entry(self, rev, entry):
711 data = entry[:10]
724 data = entry[:10]
712 data_comp = entry[10] & 3
725 data_comp = entry[10] & 3
713 sidedata_comp = (entry[11] & 3) << 2
726 sidedata_comp = (entry[11] & 3) << 2
714 data += (data_comp | sidedata_comp,)
727 data += (data_comp | sidedata_comp,)
715
728
716 return self.index_format.pack(*data)
729 return self.index_format.pack(*data)
717
730
718 def entry_binary(self, rev):
731 def entry_binary(self, rev):
719 """return the raw binary string representing a revision"""
732 """return the raw binary string representing a revision"""
720 entry = self[rev]
733 entry = self[rev]
721 return self._pack_entry(rev, entry)
734 return self._pack_entry(rev, entry)
722
735
723 def pack_header(self, header):
736 def pack_header(self, header):
724 """pack header information as binary"""
737 """pack header information as binary"""
725 msg = 'version header should go in the docket, not the index: %d'
738 msg = 'version header should go in the docket, not the index: %d'
726 msg %= header
739 msg %= header
727 raise error.ProgrammingError(msg)
740 raise error.ProgrammingError(msg)
728
741
729
742
730 class IndexChangelogV2(IndexObject2):
743 class IndexChangelogV2(IndexObject2):
731 index_format = revlog_constants.INDEX_ENTRY_CL_V2
744 index_format = revlog_constants.INDEX_ENTRY_CL_V2
732
745
733 def _unpack_entry(self, rev, data, r=True):
746 def _unpack_entry(self, rev, data, r=True):
734 items = self.index_format.unpack(data)
747 items = self.index_format.unpack(data)
735 entry = items[:3] + (rev, rev) + items[3:8]
748 entry = items[:3] + (rev, rev) + items[3:8]
736 data_comp = items[8] & 3
749 data_comp = items[8] & 3
737 sidedata_comp = (items[8] >> 2) & 3
750 sidedata_comp = (items[8] >> 2) & 3
738 return entry + (data_comp, sidedata_comp)
751 return entry + (data_comp, sidedata_comp)
739
752
740 def _pack_entry(self, rev, entry):
753 def _pack_entry(self, rev, entry):
741 assert entry[3] == rev, entry[3]
754 assert entry[3] == rev, entry[3]
742 assert entry[4] == rev, entry[4]
755 assert entry[4] == rev, entry[4]
743 data = entry[:3] + entry[5:10]
756 data = entry[:3] + entry[5:10]
744 data_comp = entry[10] & 3
757 data_comp = entry[10] & 3
745 sidedata_comp = (entry[11] & 3) << 2
758 sidedata_comp = (entry[11] & 3) << 2
746 data += (data_comp | sidedata_comp,)
759 data += (data_comp | sidedata_comp,)
747 return self.index_format.pack(*data)
760 return self.index_format.pack(*data)
748
761
749
762
750 def parse_index_devel_nodemap(data, inline):
763 def parse_index_devel_nodemap(data, inline):
751 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
764 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
752 return PersistentNodeMapIndexObject(data), None
765 return PersistentNodeMapIndexObject(data), None
753
766
754
767
755 def parse_dirstate(dmap, copymap, st):
768 def parse_dirstate(dmap, copymap, st):
756 parents = [st[:20], st[20:40]]
769 parents = [st[:20], st[20:40]]
757 # dereference fields so they will be local in loop
770 # dereference fields so they will be local in loop
758 format = b">cllll"
771 format = b">cllll"
759 e_size = struct.calcsize(format)
772 e_size = struct.calcsize(format)
760 pos1 = 40
773 pos1 = 40
761 l = len(st)
774 l = len(st)
762
775
763 # the inner loop
776 # the inner loop
764 while pos1 < l:
777 while pos1 < l:
765 pos2 = pos1 + e_size
778 pos2 = pos1 + e_size
766 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
779 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
767 pos1 = pos2 + e[4]
780 pos1 = pos2 + e[4]
768 f = st[pos2:pos1]
781 f = st[pos2:pos1]
769 if b'\0' in f:
782 if b'\0' in f:
770 f, c = f.split(b'\0')
783 f, c = f.split(b'\0')
771 copymap[f] = c
784 copymap[f] = c
772 dmap[f] = DirstateItem.from_v1_data(*e[:4])
785 dmap[f] = DirstateItem.from_v1_data(*e[:4])
773 return parents
786 return parents
774
787
775
788
776 def pack_dirstate(dmap, copymap, pl, now):
789 def pack_dirstate(dmap, copymap, pl, now):
777 now = int(now)
790 now = int(now)
778 cs = stringio()
791 cs = stringio()
779 write = cs.write
792 write = cs.write
780 write(b"".join(pl))
793 write(b"".join(pl))
781 for f, e in pycompat.iteritems(dmap):
794 for f, e in pycompat.iteritems(dmap):
782 if e.need_delay(now):
795 if e.need_delay(now):
783 # The file was last modified "simultaneously" with the current
796 # The file was last modified "simultaneously" with the current
784 # write to dirstate (i.e. within the same second for file-
797 # write to dirstate (i.e. within the same second for file-
785 # systems with a granularity of 1 sec). This commonly happens
798 # systems with a granularity of 1 sec). This commonly happens
786 # for at least a couple of files on 'update'.
799 # for at least a couple of files on 'update'.
787 # The user could change the file without changing its size
800 # The user could change the file without changing its size
788 # within the same second. Invalidate the file's mtime in
801 # within the same second. Invalidate the file's mtime in
789 # dirstate, forcing future 'status' calls to compare the
802 # dirstate, forcing future 'status' calls to compare the
790 # contents of the file if the size is the same. This prevents
803 # contents of the file if the size is the same. This prevents
791 # mistakenly treating such files as clean.
804 # mistakenly treating such files as clean.
792 e.set_possibly_dirty()
805 e.set_possibly_dirty()
793
806
794 if f in copymap:
807 if f in copymap:
795 f = b"%s\0%s" % (f, copymap[f])
808 f = b"%s\0%s" % (f, copymap[f])
796 e = _pack(
809 e = _pack(
797 b">cllll",
810 b">cllll",
798 e.v1_state(),
811 e.v1_state(),
799 e.v1_mode(),
812 e.v1_mode(),
800 e.v1_size(),
813 e.v1_size(),
801 e.v1_mtime(),
814 e.v1_mtime(),
802 len(f),
815 len(f),
803 )
816 )
804 write(e)
817 write(e)
805 write(f)
818 write(f)
806 return cs.getvalue()
819 return cs.getvalue()
@@ -1,380 +1,394 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use bitflags::bitflags;
2 use bitflags::bitflags;
3 use std::convert::TryFrom;
3 use std::convert::TryFrom;
4
4
5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
6 pub enum EntryState {
6 pub enum EntryState {
7 Normal,
7 Normal,
8 Added,
8 Added,
9 Removed,
9 Removed,
10 Merged,
10 Merged,
11 }
11 }
12
12
13 /// The C implementation uses all signed types. This will be an issue
13 /// The C implementation uses all signed types. This will be an issue
14 /// either when 4GB+ source files are commonplace or in 2038, whichever
14 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 /// comes first.
15 /// comes first.
16 #[derive(Debug, PartialEq, Copy, Clone)]
16 #[derive(Debug, PartialEq, Copy, Clone)]
17 pub struct DirstateEntry {
17 pub struct DirstateEntry {
18 flags: Flags,
18 flags: Flags,
19 mode: i32,
19 mode: i32,
20 size: i32,
20 size: i32,
21 mtime: i32,
21 mtime: i32,
22 }
22 }
23
23
24 bitflags! {
24 bitflags! {
25 pub struct Flags: u8 {
25 pub struct Flags: u8 {
26 const WDIR_TRACKED = 1 << 0;
26 const WDIR_TRACKED = 1 << 0;
27 const P1_TRACKED = 1 << 1;
27 const P1_TRACKED = 1 << 1;
28 const P2_TRACKED = 1 << 2;
28 const P2_TRACKED = 1 << 2;
29 const POSSIBLY_DIRTY = 1 << 3;
29 const POSSIBLY_DIRTY = 1 << 3;
30 const MERGED = 1 << 4;
30 const MERGED = 1 << 4;
31 const CLEAN_P1 = 1 << 5;
31 const CLEAN_P1 = 1 << 5;
32 const CLEAN_P2 = 1 << 6;
32 const CLEAN_P2 = 1 << 6;
33 const ENTRYLESS_TREE_NODE = 1 << 7;
33 const ENTRYLESS_TREE_NODE = 1 << 7;
34 }
34 }
35 }
35 }
36
36
37 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
37 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
38
38
39 pub const MTIME_UNSET: i32 = -1;
39 pub const MTIME_UNSET: i32 = -1;
40
40
41 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
41 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
42 /// other parent. This allows revert to pick the right status back during a
42 /// other parent. This allows revert to pick the right status back during a
43 /// merge.
43 /// merge.
44 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
44 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
45 /// A special value used for internal representation of special case in
45 /// A special value used for internal representation of special case in
46 /// dirstate v1 format.
46 /// dirstate v1 format.
47 pub const SIZE_NON_NORMAL: i32 = -1;
47 pub const SIZE_NON_NORMAL: i32 = -1;
48
48
49 impl DirstateEntry {
49 impl DirstateEntry {
50 pub fn new(
50 pub fn new(
51 flags: Flags,
51 flags: Flags,
52 mode_size_mtime: Option<(i32, i32, i32)>,
52 mode_size_mtime: Option<(i32, i32, i32)>,
53 ) -> Self {
53 ) -> Self {
54 let (mode, size, mtime) =
54 let (mode, size, mtime) =
55 mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET));
55 mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET));
56 Self {
56 Self {
57 flags,
57 flags,
58 mode,
58 mode,
59 size,
59 size,
60 mtime,
60 mtime,
61 }
61 }
62 }
62 }
63
63
64 pub fn from_v1_data(
64 pub fn from_v1_data(
65 state: EntryState,
65 state: EntryState,
66 mode: i32,
66 mode: i32,
67 size: i32,
67 size: i32,
68 mtime: i32,
68 mtime: i32,
69 ) -> Self {
69 ) -> Self {
70 match state {
70 match state {
71 EntryState::Normal => {
71 EntryState::Normal => {
72 if size == SIZE_FROM_OTHER_PARENT {
72 if size == SIZE_FROM_OTHER_PARENT {
73 Self::new_from_p2()
73 Self::new_from_p2()
74 } else if size == SIZE_NON_NORMAL {
74 } else if size == SIZE_NON_NORMAL {
75 Self::new_possibly_dirty()
75 Self::new_possibly_dirty()
76 } else if mtime == MTIME_UNSET {
76 } else if mtime == MTIME_UNSET {
77 Self {
77 Self {
78 flags: Flags::WDIR_TRACKED
78 flags: Flags::WDIR_TRACKED
79 | Flags::P1_TRACKED
79 | Flags::P1_TRACKED
80 | Flags::POSSIBLY_DIRTY,
80 | Flags::POSSIBLY_DIRTY,
81 mode,
81 mode,
82 size,
82 size,
83 mtime: 0,
83 mtime: 0,
84 }
84 }
85 } else {
85 } else {
86 Self::new_normal(mode, size, mtime)
86 Self::new_normal(mode, size, mtime)
87 }
87 }
88 }
88 }
89 EntryState::Added => Self::new_added(),
89 EntryState::Added => Self::new_added(),
90 EntryState::Removed => Self {
90 EntryState::Removed => Self {
91 flags: if size == SIZE_NON_NORMAL {
91 flags: if size == SIZE_NON_NORMAL {
92 Flags::P1_TRACKED // might not be true because of rename ?
92 Flags::P1_TRACKED // might not be true because of rename ?
93 | Flags::P2_TRACKED // might not be true because of rename ?
93 | Flags::P2_TRACKED // might not be true because of rename ?
94 | Flags::MERGED
94 | Flags::MERGED
95 } else if size == SIZE_FROM_OTHER_PARENT {
95 } else if size == SIZE_FROM_OTHER_PARENT {
96 // We don’t know if P1_TRACKED should be set (file history)
96 // We don’t know if P1_TRACKED should be set (file history)
97 Flags::P2_TRACKED | Flags::CLEAN_P2
97 Flags::P2_TRACKED | Flags::CLEAN_P2
98 } else {
98 } else {
99 Flags::P1_TRACKED
99 Flags::P1_TRACKED
100 },
100 },
101 mode: 0,
101 mode: 0,
102 size: 0,
102 size: 0,
103 mtime: 0,
103 mtime: 0,
104 },
104 },
105 EntryState::Merged => Self::new_merged(),
105 EntryState::Merged => Self::new_merged(),
106 }
106 }
107 }
107 }
108
108
109 pub fn new_from_p2() -> Self {
109 pub fn new_from_p2() -> Self {
110 Self {
110 Self {
111 // might be missing P1_TRACKED
111 // might be missing P1_TRACKED
112 flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2,
112 flags: Flags::WDIR_TRACKED | Flags::P2_TRACKED | Flags::CLEAN_P2,
113 mode: 0,
113 mode: 0,
114 size: SIZE_FROM_OTHER_PARENT,
114 size: SIZE_FROM_OTHER_PARENT,
115 mtime: MTIME_UNSET,
115 mtime: MTIME_UNSET,
116 }
116 }
117 }
117 }
118
118
119 pub fn new_possibly_dirty() -> Self {
119 pub fn new_possibly_dirty() -> Self {
120 Self {
120 Self {
121 flags: Flags::WDIR_TRACKED
121 flags: Flags::WDIR_TRACKED
122 | Flags::P1_TRACKED
122 | Flags::P1_TRACKED
123 | Flags::POSSIBLY_DIRTY,
123 | Flags::POSSIBLY_DIRTY,
124 mode: 0,
124 mode: 0,
125 size: SIZE_NON_NORMAL,
125 size: SIZE_NON_NORMAL,
126 mtime: MTIME_UNSET,
126 mtime: MTIME_UNSET,
127 }
127 }
128 }
128 }
129
129
130 pub fn new_added() -> Self {
130 pub fn new_added() -> Self {
131 Self {
131 Self {
132 flags: Flags::WDIR_TRACKED,
132 flags: Flags::WDIR_TRACKED,
133 mode: 0,
133 mode: 0,
134 size: SIZE_NON_NORMAL,
134 size: SIZE_NON_NORMAL,
135 mtime: MTIME_UNSET,
135 mtime: MTIME_UNSET,
136 }
136 }
137 }
137 }
138
138
139 pub fn new_merged() -> Self {
139 pub fn new_merged() -> Self {
140 Self {
140 Self {
141 flags: Flags::WDIR_TRACKED
141 flags: Flags::WDIR_TRACKED
142 | Flags::P1_TRACKED // might not be true because of rename ?
142 | Flags::P1_TRACKED // might not be true because of rename ?
143 | Flags::P2_TRACKED // might not be true because of rename ?
143 | Flags::P2_TRACKED // might not be true because of rename ?
144 | Flags::MERGED,
144 | Flags::MERGED,
145 mode: 0,
145 mode: 0,
146 size: SIZE_NON_NORMAL,
146 size: SIZE_NON_NORMAL,
147 mtime: MTIME_UNSET,
147 mtime: MTIME_UNSET,
148 }
148 }
149 }
149 }
150
150
151 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
151 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
152 Self {
152 Self {
153 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
153 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
154 mode,
154 mode,
155 size,
155 size,
156 mtime,
156 mtime,
157 }
157 }
158 }
158 }
159
159
160 /// Creates a new entry in "removed" state.
160 /// Creates a new entry in "removed" state.
161 ///
161 ///
162 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
162 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
163 /// `SIZE_FROM_OTHER_PARENT`
163 /// `SIZE_FROM_OTHER_PARENT`
164 pub fn new_removed(size: i32) -> Self {
164 pub fn new_removed(size: i32) -> Self {
165 Self::from_v1_data(EntryState::Removed, 0, size, 0)
165 Self::from_v1_data(EntryState::Removed, 0, size, 0)
166 }
166 }
167
167
168 pub fn tracked(&self) -> bool {
168 pub fn tracked(&self) -> bool {
169 self.flags.contains(Flags::WDIR_TRACKED)
169 self.flags.contains(Flags::WDIR_TRACKED)
170 }
170 }
171
171
172 fn tracked_in_any_parent(&self) -> bool {
172 fn tracked_in_any_parent(&self) -> bool {
173 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED)
173 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED)
174 }
174 }
175
175
176 pub fn removed(&self) -> bool {
176 pub fn removed(&self) -> bool {
177 self.tracked_in_any_parent()
177 self.tracked_in_any_parent()
178 && !self.flags.contains(Flags::WDIR_TRACKED)
178 && !self.flags.contains(Flags::WDIR_TRACKED)
179 }
179 }
180
180
181 pub fn merged(&self) -> bool {
181 pub fn merged(&self) -> bool {
182 self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED)
182 self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED)
183 }
183 }
184
184
185 pub fn added(&self) -> bool {
185 pub fn added(&self) -> bool {
186 self.flags.contains(Flags::WDIR_TRACKED)
186 self.flags.contains(Flags::WDIR_TRACKED)
187 && !self.tracked_in_any_parent()
187 && !self.tracked_in_any_parent()
188 }
188 }
189
189
190 pub fn from_p2(&self) -> bool {
190 pub fn from_p2(&self) -> bool {
191 self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2)
191 self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2)
192 }
192 }
193
193
194 pub fn maybe_clean(&self) -> bool {
195 if !self.flags.contains(Flags::WDIR_TRACKED) {
196 false
197 } else if self.added() {
198 false
199 } else if self.flags.contains(Flags::MERGED) {
200 false
201 } else if self.flags.contains(Flags::CLEAN_P2) {
202 false
203 } else {
204 true
205 }
206 }
207
194 pub fn state(&self) -> EntryState {
208 pub fn state(&self) -> EntryState {
195 if self.removed() {
209 if self.removed() {
196 EntryState::Removed
210 EntryState::Removed
197 } else if self.merged() {
211 } else if self.merged() {
198 EntryState::Merged
212 EntryState::Merged
199 } else if self.added() {
213 } else if self.added() {
200 EntryState::Added
214 EntryState::Added
201 } else {
215 } else {
202 EntryState::Normal
216 EntryState::Normal
203 }
217 }
204 }
218 }
205
219
206 pub fn mode(&self) -> i32 {
220 pub fn mode(&self) -> i32 {
207 self.mode
221 self.mode
208 }
222 }
209
223
210 pub fn size(&self) -> i32 {
224 pub fn size(&self) -> i32 {
211 if self.removed() && self.flags.contains(Flags::MERGED) {
225 if self.removed() && self.flags.contains(Flags::MERGED) {
212 SIZE_NON_NORMAL
226 SIZE_NON_NORMAL
213 } else if self.removed() && self.flags.contains(Flags::CLEAN_P2) {
227 } else if self.removed() && self.flags.contains(Flags::CLEAN_P2) {
214 SIZE_FROM_OTHER_PARENT
228 SIZE_FROM_OTHER_PARENT
215 } else if self.removed() {
229 } else if self.removed() {
216 0
230 0
217 } else if self.merged() {
231 } else if self.merged() {
218 SIZE_FROM_OTHER_PARENT
232 SIZE_FROM_OTHER_PARENT
219 } else if self.added() {
233 } else if self.added() {
220 SIZE_NON_NORMAL
234 SIZE_NON_NORMAL
221 } else if self.from_p2() {
235 } else if self.from_p2() {
222 SIZE_FROM_OTHER_PARENT
236 SIZE_FROM_OTHER_PARENT
223 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
237 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
224 self.size // TODO: SIZE_NON_NORMAL ?
238 self.size // TODO: SIZE_NON_NORMAL ?
225 } else {
239 } else {
226 self.size
240 self.size
227 }
241 }
228 }
242 }
229
243
230 pub fn mtime(&self) -> i32 {
244 pub fn mtime(&self) -> i32 {
231 if self.removed() {
245 if self.removed() {
232 0
246 0
233 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
247 } else if self.flags.contains(Flags::POSSIBLY_DIRTY) {
234 MTIME_UNSET
248 MTIME_UNSET
235 } else if self.merged() {
249 } else if self.merged() {
236 MTIME_UNSET
250 MTIME_UNSET
237 } else if self.added() {
251 } else if self.added() {
238 MTIME_UNSET
252 MTIME_UNSET
239 } else if self.from_p2() {
253 } else if self.from_p2() {
240 MTIME_UNSET
254 MTIME_UNSET
241 } else {
255 } else {
242 self.mtime
256 self.mtime
243 }
257 }
244 }
258 }
245
259
246 pub fn set_possibly_dirty(&mut self) {
260 pub fn set_possibly_dirty(&mut self) {
247 self.flags.insert(Flags::POSSIBLY_DIRTY)
261 self.flags.insert(Flags::POSSIBLY_DIRTY)
248 }
262 }
249
263
250 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
264 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
251 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
265 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
252 self.flags.remove(
266 self.flags.remove(
253 Flags::P2_TRACKED // This might be wrong
267 Flags::P2_TRACKED // This might be wrong
254 | Flags::MERGED
268 | Flags::MERGED
255 | Flags::CLEAN_P2
269 | Flags::CLEAN_P2
256 | Flags::POSSIBLY_DIRTY,
270 | Flags::POSSIBLY_DIRTY,
257 );
271 );
258 self.mode = mode;
272 self.mode = mode;
259 self.size = size;
273 self.size = size;
260 self.mtime = mtime;
274 self.mtime = mtime;
261 }
275 }
262
276
263 pub fn set_tracked(&mut self) {
277 pub fn set_tracked(&mut self) {
264 self.flags
278 self.flags
265 .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY);
279 .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY);
266 // size = None on the python size turn into size = NON_NORMAL when
280 // size = None on the python size turn into size = NON_NORMAL when
267 // accessed. So the next line is currently required, but a some future
281 // accessed. So the next line is currently required, but a some future
268 // clean up would be welcome.
282 // clean up would be welcome.
269 self.size = SIZE_NON_NORMAL;
283 self.size = SIZE_NON_NORMAL;
270 }
284 }
271
285
272 pub fn set_untracked(&mut self) {
286 pub fn set_untracked(&mut self) {
273 self.flags.remove(Flags::WDIR_TRACKED);
287 self.flags.remove(Flags::WDIR_TRACKED);
274 self.mode = 0;
288 self.mode = 0;
275 self.size = 0;
289 self.size = 0;
276 self.mtime = 0;
290 self.mtime = 0;
277 }
291 }
278
292
279 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
293 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
280 /// in the dirstate-v1 format.
294 /// in the dirstate-v1 format.
281 ///
295 ///
282 /// This includes marker values such as `mtime == -1`. In the future we may
296 /// This includes marker values such as `mtime == -1`. In the future we may
283 /// want to not represent these cases that way in memory, but serialization
297 /// want to not represent these cases that way in memory, but serialization
284 /// will need to keep the same format.
298 /// will need to keep the same format.
285 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
299 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
286 (self.state().into(), self.mode(), self.size(), self.mtime())
300 (self.state().into(), self.mode(), self.size(), self.mtime())
287 }
301 }
288
302
289 pub(crate) fn is_from_other_parent(&self) -> bool {
303 pub(crate) fn is_from_other_parent(&self) -> bool {
290 self.state() == EntryState::Normal
304 self.state() == EntryState::Normal
291 && self.size() == SIZE_FROM_OTHER_PARENT
305 && self.size() == SIZE_FROM_OTHER_PARENT
292 }
306 }
293
307
294 // TODO: other platforms
308 // TODO: other platforms
295 #[cfg(unix)]
309 #[cfg(unix)]
296 pub fn mode_changed(
310 pub fn mode_changed(
297 &self,
311 &self,
298 filesystem_metadata: &std::fs::Metadata,
312 filesystem_metadata: &std::fs::Metadata,
299 ) -> bool {
313 ) -> bool {
300 use std::os::unix::fs::MetadataExt;
314 use std::os::unix::fs::MetadataExt;
301 const EXEC_BIT_MASK: u32 = 0o100;
315 const EXEC_BIT_MASK: u32 = 0o100;
302 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
316 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
303 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
317 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
304 dirstate_exec_bit != fs_exec_bit
318 dirstate_exec_bit != fs_exec_bit
305 }
319 }
306
320
307 /// Returns a `(state, mode, size, mtime)` tuple as for
321 /// Returns a `(state, mode, size, mtime)` tuple as for
308 /// `DirstateMapMethods::debug_iter`.
322 /// `DirstateMapMethods::debug_iter`.
309 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
323 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
310 let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) {
324 let state = if self.flags.contains(Flags::ENTRYLESS_TREE_NODE) {
311 b' '
325 b' '
312 } else {
326 } else {
313 self.state().into()
327 self.state().into()
314 };
328 };
315 (state, self.mode(), self.size(), self.mtime())
329 (state, self.mode(), self.size(), self.mtime())
316 }
330 }
317
331
318 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
332 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
319 self.state() == EntryState::Normal && self.mtime() == now
333 self.state() == EntryState::Normal && self.mtime() == now
320 }
334 }
321
335
322 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
336 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
323 let ambiguous = self.mtime_is_ambiguous(now);
337 let ambiguous = self.mtime_is_ambiguous(now);
324 if ambiguous {
338 if ambiguous {
325 // The file was last modified "simultaneously" with the current
339 // The file was last modified "simultaneously" with the current
326 // write to dirstate (i.e. within the same second for file-
340 // write to dirstate (i.e. within the same second for file-
327 // systems with a granularity of 1 sec). This commonly happens
341 // systems with a granularity of 1 sec). This commonly happens
328 // for at least a couple of files on 'update'.
342 // for at least a couple of files on 'update'.
329 // The user could change the file without changing its size
343 // The user could change the file without changing its size
330 // within the same second. Invalidate the file's mtime in
344 // within the same second. Invalidate the file's mtime in
331 // dirstate, forcing future 'status' calls to compare the
345 // dirstate, forcing future 'status' calls to compare the
332 // contents of the file if the size is the same. This prevents
346 // contents of the file if the size is the same. This prevents
333 // mistakenly treating such files as clean.
347 // mistakenly treating such files as clean.
334 self.clear_mtime()
348 self.clear_mtime()
335 }
349 }
336 ambiguous
350 ambiguous
337 }
351 }
338
352
339 pub fn clear_mtime(&mut self) {
353 pub fn clear_mtime(&mut self) {
340 self.mtime = -1;
354 self.mtime = -1;
341 }
355 }
342 }
356 }
343
357
344 impl EntryState {
358 impl EntryState {
345 pub fn is_tracked(self) -> bool {
359 pub fn is_tracked(self) -> bool {
346 use EntryState::*;
360 use EntryState::*;
347 match self {
361 match self {
348 Normal | Added | Merged => true,
362 Normal | Added | Merged => true,
349 Removed => false,
363 Removed => false,
350 }
364 }
351 }
365 }
352 }
366 }
353
367
354 impl TryFrom<u8> for EntryState {
368 impl TryFrom<u8> for EntryState {
355 type Error = HgError;
369 type Error = HgError;
356
370
357 fn try_from(value: u8) -> Result<Self, Self::Error> {
371 fn try_from(value: u8) -> Result<Self, Self::Error> {
358 match value {
372 match value {
359 b'n' => Ok(EntryState::Normal),
373 b'n' => Ok(EntryState::Normal),
360 b'a' => Ok(EntryState::Added),
374 b'a' => Ok(EntryState::Added),
361 b'r' => Ok(EntryState::Removed),
375 b'r' => Ok(EntryState::Removed),
362 b'm' => Ok(EntryState::Merged),
376 b'm' => Ok(EntryState::Merged),
363 _ => Err(HgError::CorruptedRepository(format!(
377 _ => Err(HgError::CorruptedRepository(format!(
364 "Incorrect dirstate entry state {}",
378 "Incorrect dirstate entry state {}",
365 value
379 value
366 ))),
380 ))),
367 }
381 }
368 }
382 }
369 }
383 }
370
384
371 impl Into<u8> for EntryState {
385 impl Into<u8> for EntryState {
372 fn into(self) -> u8 {
386 fn into(self) -> u8 {
373 match self {
387 match self {
374 EntryState::Normal => b'n',
388 EntryState::Normal => b'n',
375 EntryState::Added => b'a',
389 EntryState::Added => b'a',
376 EntryState::Removed => b'r',
390 EntryState::Removed => b'r',
377 EntryState::Merged => b'm',
391 EntryState::Merged => b'm',
378 }
392 }
379 }
393 }
380 }
394 }
@@ -1,203 +1,208 b''
1 use cpython::exc;
1 use cpython::exc;
2 use cpython::PyBytes;
2 use cpython::PyBytes;
3 use cpython::PyErr;
3 use cpython::PyErr;
4 use cpython::PyNone;
4 use cpython::PyNone;
5 use cpython::PyObject;
5 use cpython::PyObject;
6 use cpython::PyResult;
6 use cpython::PyResult;
7 use cpython::Python;
7 use cpython::Python;
8 use cpython::PythonObject;
8 use cpython::PythonObject;
9 use hg::dirstate::entry::Flags;
9 use hg::dirstate::entry::Flags;
10 use hg::dirstate::DirstateEntry;
10 use hg::dirstate::DirstateEntry;
11 use hg::dirstate::EntryState;
11 use hg::dirstate::EntryState;
12 use std::cell::Cell;
12 use std::cell::Cell;
13 use std::convert::TryFrom;
13 use std::convert::TryFrom;
14
14
15 py_class!(pub class DirstateItem |py| {
15 py_class!(pub class DirstateItem |py| {
16 data entry: Cell<DirstateEntry>;
16 data entry: Cell<DirstateEntry>;
17
17
18 def __new__(
18 def __new__(
19 _cls,
19 _cls,
20 wc_tracked: bool = false,
20 wc_tracked: bool = false,
21 p1_tracked: bool = false,
21 p1_tracked: bool = false,
22 p2_tracked: bool = false,
22 p2_tracked: bool = false,
23 merged: bool = false,
23 merged: bool = false,
24 clean_p1: bool = false,
24 clean_p1: bool = false,
25 clean_p2: bool = false,
25 clean_p2: bool = false,
26 possibly_dirty: bool = false,
26 possibly_dirty: bool = false,
27 parentfiledata: Option<(i32, i32, i32)> = None,
27 parentfiledata: Option<(i32, i32, i32)> = None,
28
28
29 ) -> PyResult<DirstateItem> {
29 ) -> PyResult<DirstateItem> {
30 let mut flags = Flags::empty();
30 let mut flags = Flags::empty();
31 flags.set(Flags::WDIR_TRACKED, wc_tracked);
31 flags.set(Flags::WDIR_TRACKED, wc_tracked);
32 flags.set(Flags::P1_TRACKED, p1_tracked);
32 flags.set(Flags::P1_TRACKED, p1_tracked);
33 flags.set(Flags::P2_TRACKED, p2_tracked);
33 flags.set(Flags::P2_TRACKED, p2_tracked);
34 flags.set(Flags::MERGED, merged);
34 flags.set(Flags::MERGED, merged);
35 flags.set(Flags::CLEAN_P1, clean_p1);
35 flags.set(Flags::CLEAN_P1, clean_p1);
36 flags.set(Flags::CLEAN_P2, clean_p2);
36 flags.set(Flags::CLEAN_P2, clean_p2);
37 flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty);
37 flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty);
38 let entry = DirstateEntry::new(flags, parentfiledata);
38 let entry = DirstateEntry::new(flags, parentfiledata);
39 DirstateItem::create_instance(py, Cell::new(entry))
39 DirstateItem::create_instance(py, Cell::new(entry))
40 }
40 }
41
41
42 @property
42 @property
43 def state(&self) -> PyResult<PyBytes> {
43 def state(&self) -> PyResult<PyBytes> {
44 let state_byte: u8 = self.entry(py).get().state().into();
44 let state_byte: u8 = self.entry(py).get().state().into();
45 Ok(PyBytes::new(py, &[state_byte]))
45 Ok(PyBytes::new(py, &[state_byte]))
46 }
46 }
47
47
48 @property
48 @property
49 def mode(&self) -> PyResult<i32> {
49 def mode(&self) -> PyResult<i32> {
50 Ok(self.entry(py).get().mode())
50 Ok(self.entry(py).get().mode())
51 }
51 }
52
52
53 @property
53 @property
54 def size(&self) -> PyResult<i32> {
54 def size(&self) -> PyResult<i32> {
55 Ok(self.entry(py).get().size())
55 Ok(self.entry(py).get().size())
56 }
56 }
57
57
58 @property
58 @property
59 def mtime(&self) -> PyResult<i32> {
59 def mtime(&self) -> PyResult<i32> {
60 Ok(self.entry(py).get().mtime())
60 Ok(self.entry(py).get().mtime())
61 }
61 }
62
62
63 @property
63 @property
64 def tracked(&self) -> PyResult<bool> {
64 def tracked(&self) -> PyResult<bool> {
65 Ok(self.entry(py).get().tracked())
65 Ok(self.entry(py).get().tracked())
66 }
66 }
67
67
68 @property
68 @property
69 def added(&self) -> PyResult<bool> {
69 def added(&self) -> PyResult<bool> {
70 Ok(self.entry(py).get().added())
70 Ok(self.entry(py).get().added())
71 }
71 }
72
72
73 @property
73 @property
74 def merged(&self) -> PyResult<bool> {
74 def merged(&self) -> PyResult<bool> {
75 Ok(self.entry(py).get().merged())
75 Ok(self.entry(py).get().merged())
76 }
76 }
77
77
78 @property
78 @property
79 def removed(&self) -> PyResult<bool> {
79 def removed(&self) -> PyResult<bool> {
80 Ok(self.entry(py).get().removed())
80 Ok(self.entry(py).get().removed())
81 }
81 }
82
82
83 @property
83 @property
84 def from_p2(&self) -> PyResult<bool> {
84 def from_p2(&self) -> PyResult<bool> {
85 Ok(self.entry(py).get().from_p2())
85 Ok(self.entry(py).get().from_p2())
86 }
86 }
87
87
88 @property
89 def maybe_clean(&self) -> PyResult<bool> {
90 Ok(self.entry(py).get().maybe_clean())
91 }
92
88 def v1_state(&self) -> PyResult<PyBytes> {
93 def v1_state(&self) -> PyResult<PyBytes> {
89 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
94 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
90 let state_byte: u8 = state.into();
95 let state_byte: u8 = state.into();
91 Ok(PyBytes::new(py, &[state_byte]))
96 Ok(PyBytes::new(py, &[state_byte]))
92 }
97 }
93
98
94 def v1_mode(&self) -> PyResult<i32> {
99 def v1_mode(&self) -> PyResult<i32> {
95 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
100 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
96 Ok(mode)
101 Ok(mode)
97 }
102 }
98
103
99 def v1_size(&self) -> PyResult<i32> {
104 def v1_size(&self) -> PyResult<i32> {
100 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
105 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
101 Ok(size)
106 Ok(size)
102 }
107 }
103
108
104 def v1_mtime(&self) -> PyResult<i32> {
109 def v1_mtime(&self) -> PyResult<i32> {
105 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
110 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
106 Ok(mtime)
111 Ok(mtime)
107 }
112 }
108
113
109 def need_delay(&self, now: i32) -> PyResult<bool> {
114 def need_delay(&self, now: i32) -> PyResult<bool> {
110 Ok(self.entry(py).get().mtime_is_ambiguous(now))
115 Ok(self.entry(py).get().mtime_is_ambiguous(now))
111 }
116 }
112
117
113 @classmethod
118 @classmethod
114 def from_v1_data(
119 def from_v1_data(
115 _cls,
120 _cls,
116 state: PyBytes,
121 state: PyBytes,
117 mode: i32,
122 mode: i32,
118 size: i32,
123 size: i32,
119 mtime: i32,
124 mtime: i32,
120 ) -> PyResult<Self> {
125 ) -> PyResult<Self> {
121 let state = <[u8; 1]>::try_from(state.data(py))
126 let state = <[u8; 1]>::try_from(state.data(py))
122 .ok()
127 .ok()
123 .and_then(|state| EntryState::try_from(state[0]).ok())
128 .and_then(|state| EntryState::try_from(state[0]).ok())
124 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
129 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
125 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
130 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
126 DirstateItem::create_instance(py, Cell::new(entry))
131 DirstateItem::create_instance(py, Cell::new(entry))
127 }
132 }
128
133
129 @classmethod
134 @classmethod
130 def new_added(_cls) -> PyResult<Self> {
135 def new_added(_cls) -> PyResult<Self> {
131 let entry = DirstateEntry::new_added();
136 let entry = DirstateEntry::new_added();
132 DirstateItem::create_instance(py, Cell::new(entry))
137 DirstateItem::create_instance(py, Cell::new(entry))
133 }
138 }
134
139
135 @classmethod
140 @classmethod
136 def new_merged(_cls) -> PyResult<Self> {
141 def new_merged(_cls) -> PyResult<Self> {
137 let entry = DirstateEntry::new_merged();
142 let entry = DirstateEntry::new_merged();
138 DirstateItem::create_instance(py, Cell::new(entry))
143 DirstateItem::create_instance(py, Cell::new(entry))
139 }
144 }
140
145
141 @classmethod
146 @classmethod
142 def new_from_p2(_cls) -> PyResult<Self> {
147 def new_from_p2(_cls) -> PyResult<Self> {
143 let entry = DirstateEntry::new_from_p2();
148 let entry = DirstateEntry::new_from_p2();
144 DirstateItem::create_instance(py, Cell::new(entry))
149 DirstateItem::create_instance(py, Cell::new(entry))
145 }
150 }
146
151
147 @classmethod
152 @classmethod
148 def new_possibly_dirty(_cls) -> PyResult<Self> {
153 def new_possibly_dirty(_cls) -> PyResult<Self> {
149 let entry = DirstateEntry::new_possibly_dirty();
154 let entry = DirstateEntry::new_possibly_dirty();
150 DirstateItem::create_instance(py, Cell::new(entry))
155 DirstateItem::create_instance(py, Cell::new(entry))
151 }
156 }
152
157
153 @classmethod
158 @classmethod
154 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
159 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
155 let entry = DirstateEntry::new_normal(mode, size, mtime);
160 let entry = DirstateEntry::new_normal(mode, size, mtime);
156 DirstateItem::create_instance(py, Cell::new(entry))
161 DirstateItem::create_instance(py, Cell::new(entry))
157 }
162 }
158
163
159 def set_clean(
164 def set_clean(
160 &self,
165 &self,
161 mode: i32,
166 mode: i32,
162 size: i32,
167 size: i32,
163 mtime: i32,
168 mtime: i32,
164 ) -> PyResult<PyNone> {
169 ) -> PyResult<PyNone> {
165 self.update(py, |entry| entry.set_clean(mode, size, mtime));
170 self.update(py, |entry| entry.set_clean(mode, size, mtime));
166 Ok(PyNone)
171 Ok(PyNone)
167 }
172 }
168
173
169 def set_possibly_dirty(&self) -> PyResult<PyNone> {
174 def set_possibly_dirty(&self) -> PyResult<PyNone> {
170 self.update(py, |entry| entry.set_possibly_dirty());
175 self.update(py, |entry| entry.set_possibly_dirty());
171 Ok(PyNone)
176 Ok(PyNone)
172 }
177 }
173
178
174 def set_tracked(&self) -> PyResult<PyNone> {
179 def set_tracked(&self) -> PyResult<PyNone> {
175 self.update(py, |entry| entry.set_tracked());
180 self.update(py, |entry| entry.set_tracked());
176 Ok(PyNone)
181 Ok(PyNone)
177 }
182 }
178
183
179 def set_untracked(&self) -> PyResult<PyNone> {
184 def set_untracked(&self) -> PyResult<PyNone> {
180 self.update(py, |entry| entry.set_untracked());
185 self.update(py, |entry| entry.set_untracked());
181 Ok(PyNone)
186 Ok(PyNone)
182 }
187 }
183 });
188 });
184
189
185 impl DirstateItem {
190 impl DirstateItem {
186 pub fn new_as_pyobject(
191 pub fn new_as_pyobject(
187 py: Python<'_>,
192 py: Python<'_>,
188 entry: DirstateEntry,
193 entry: DirstateEntry,
189 ) -> PyResult<PyObject> {
194 ) -> PyResult<PyObject> {
190 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
195 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
191 }
196 }
192
197
193 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
198 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
194 self.entry(py).get()
199 self.entry(py).get()
195 }
200 }
196
201
197 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
202 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
198 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
203 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
199 let mut entry = self.entry(py).get();
204 let mut entry = self.entry(py).get();
200 f(&mut entry);
205 f(&mut entry);
201 self.entry(py).set(entry)
206 self.entry(py).set(entry)
202 }
207 }
203 }
208 }
General Comments 0
You need to be logged in to leave comments. Login now