##// END OF EJS Templates
dirstate: make DirstateItem constructor accept fallback value...
marmoute -
r49069:948570aa default
parent child Browse files
Show More
@@ -1,1273 +1,1287 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_info;
55 int p2_info;
56 int has_meaningful_data;
56 int has_meaningful_data;
57 int has_meaningful_mtime;
57 int has_meaningful_mtime;
58 int mode;
58 int mode;
59 int size;
59 int size;
60 int mtime;
60 int mtime;
61 PyObject *parentfiledata;
61 PyObject *parentfiledata;
62 PyObject *fallback_exec;
63 PyObject *fallback_symlink;
62 static char *keywords_name[] = {
64 static char *keywords_name[] = {
63 "wc_tracked",
65 "wc_tracked", "p1_tracked", "p2_info",
64 "p1_tracked",
66 "has_meaningful_data", "has_meaningful_mtime", "parentfiledata",
65 "p2_info",
67 "fallback_exec", "fallback_symlink", NULL,
66 "has_meaningful_data",
67 "has_meaningful_mtime",
68 "parentfiledata",
69 NULL,
70 };
68 };
71 wc_tracked = 0;
69 wc_tracked = 0;
72 p1_tracked = 0;
70 p1_tracked = 0;
73 p2_info = 0;
71 p2_info = 0;
74 has_meaningful_mtime = 1;
72 has_meaningful_mtime = 1;
75 has_meaningful_data = 1;
73 has_meaningful_data = 1;
76 parentfiledata = Py_None;
74 parentfiledata = Py_None;
77 if (!PyArg_ParseTupleAndKeywords(
75 fallback_exec = Py_None;
78 args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked,
76 fallback_symlink = Py_None;
79 &p2_info, &has_meaningful_data, &has_meaningful_mtime,
77 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiiiiOOO", keywords_name,
80 &parentfiledata)) {
78 &wc_tracked, &p1_tracked, &p2_info,
79 &has_meaningful_data,
80 &has_meaningful_mtime, &parentfiledata,
81 &fallback_exec, &fallback_symlink)) {
81 return NULL;
82 return NULL;
82 }
83 }
83 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
84 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
84 if (!t) {
85 if (!t) {
85 return NULL;
86 return NULL;
86 }
87 }
87
88
88 t->flags = 0;
89 t->flags = 0;
89 if (wc_tracked) {
90 if (wc_tracked) {
90 t->flags |= dirstate_flag_wc_tracked;
91 t->flags |= dirstate_flag_wc_tracked;
91 }
92 }
92 if (p1_tracked) {
93 if (p1_tracked) {
93 t->flags |= dirstate_flag_p1_tracked;
94 t->flags |= dirstate_flag_p1_tracked;
94 }
95 }
95 if (p2_info) {
96 if (p2_info) {
96 t->flags |= dirstate_flag_p2_info;
97 t->flags |= dirstate_flag_p2_info;
97 }
98 }
98
99
100 if (fallback_exec != Py_None) {
101 t->flags |= dirstate_flag_has_fallback_exec;
102 if (PyObject_IsTrue(fallback_exec)) {
103 t->flags |= dirstate_flag_fallback_exec;
104 }
105 }
106 if (fallback_symlink != Py_None) {
107 t->flags |= dirstate_flag_has_fallback_symlink;
108 if (PyObject_IsTrue(fallback_symlink)) {
109 t->flags |= dirstate_flag_fallback_symlink;
110 }
111 }
112
99 if (parentfiledata != Py_None) {
113 if (parentfiledata != Py_None) {
100 if (!PyTuple_CheckExact(parentfiledata)) {
114 if (!PyTuple_CheckExact(parentfiledata)) {
101 PyErr_SetString(
115 PyErr_SetString(
102 PyExc_TypeError,
116 PyExc_TypeError,
103 "parentfiledata should be a Tuple or None");
117 "parentfiledata should be a Tuple or None");
104 return NULL;
118 return NULL;
105 }
119 }
106 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
120 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
107 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
121 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
108 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
122 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
109 } else {
123 } else {
110 has_meaningful_data = 0;
124 has_meaningful_data = 0;
111 has_meaningful_mtime = 0;
125 has_meaningful_mtime = 0;
112 }
126 }
113 if (has_meaningful_data) {
127 if (has_meaningful_data) {
114 t->flags |= dirstate_flag_has_meaningful_data;
128 t->flags |= dirstate_flag_has_meaningful_data;
115 t->mode = mode;
129 t->mode = mode;
116 t->size = size;
130 t->size = size;
117 } else {
131 } else {
118 t->mode = 0;
132 t->mode = 0;
119 t->size = 0;
133 t->size = 0;
120 }
134 }
121 if (has_meaningful_mtime) {
135 if (has_meaningful_mtime) {
122 t->flags |= dirstate_flag_has_file_mtime;
136 t->flags |= dirstate_flag_has_file_mtime;
123 t->mtime = mtime;
137 t->mtime = mtime;
124 } else {
138 } else {
125 t->mtime = 0;
139 t->mtime = 0;
126 }
140 }
127 return (PyObject *)t;
141 return (PyObject *)t;
128 }
142 }
129
143
130 static void dirstate_item_dealloc(PyObject *o)
144 static void dirstate_item_dealloc(PyObject *o)
131 {
145 {
132 PyObject_Del(o);
146 PyObject_Del(o);
133 }
147 }
134
148
135 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
149 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
136 {
150 {
137 return (self->flags & dirstate_flag_wc_tracked);
151 return (self->flags & dirstate_flag_wc_tracked);
138 }
152 }
139
153
140 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
154 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
141 {
155 {
142 const int mask = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
156 const int mask = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
143 dirstate_flag_p2_info;
157 dirstate_flag_p2_info;
144 return (self->flags & mask);
158 return (self->flags & mask);
145 }
159 }
146
160
147 static inline bool dirstate_item_c_added(dirstateItemObject *self)
161 static inline bool dirstate_item_c_added(dirstateItemObject *self)
148 {
162 {
149 const int mask = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
163 const int mask = (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
150 dirstate_flag_p2_info);
164 dirstate_flag_p2_info);
151 const int target = dirstate_flag_wc_tracked;
165 const int target = dirstate_flag_wc_tracked;
152 return (self->flags & mask) == target;
166 return (self->flags & mask) == target;
153 }
167 }
154
168
155 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
169 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
156 {
170 {
157 if (self->flags & dirstate_flag_wc_tracked) {
171 if (self->flags & dirstate_flag_wc_tracked) {
158 return false;
172 return false;
159 }
173 }
160 return (self->flags &
174 return (self->flags &
161 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
175 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
162 }
176 }
163
177
164 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
178 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
165 {
179 {
166 return ((self->flags & dirstate_flag_wc_tracked) &&
180 return ((self->flags & dirstate_flag_wc_tracked) &&
167 (self->flags & dirstate_flag_p1_tracked) &&
181 (self->flags & dirstate_flag_p1_tracked) &&
168 (self->flags & dirstate_flag_p2_info));
182 (self->flags & dirstate_flag_p2_info));
169 }
183 }
170
184
171 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
185 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
172 {
186 {
173 return ((self->flags & dirstate_flag_wc_tracked) &&
187 return ((self->flags & dirstate_flag_wc_tracked) &&
174 !(self->flags & dirstate_flag_p1_tracked) &&
188 !(self->flags & dirstate_flag_p1_tracked) &&
175 (self->flags & dirstate_flag_p2_info));
189 (self->flags & dirstate_flag_p2_info));
176 }
190 }
177
191
178 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
192 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
179 {
193 {
180 if (dirstate_item_c_removed(self)) {
194 if (dirstate_item_c_removed(self)) {
181 return 'r';
195 return 'r';
182 } else if (dirstate_item_c_merged(self)) {
196 } else if (dirstate_item_c_merged(self)) {
183 return 'm';
197 return 'm';
184 } else if (dirstate_item_c_added(self)) {
198 } else if (dirstate_item_c_added(self)) {
185 return 'a';
199 return 'a';
186 } else {
200 } else {
187 return 'n';
201 return 'n';
188 }
202 }
189 }
203 }
190
204
191 static inline bool dirstate_item_c_has_fallback_exec(dirstateItemObject *self)
205 static inline bool dirstate_item_c_has_fallback_exec(dirstateItemObject *self)
192 {
206 {
193 return (bool)self->flags & dirstate_flag_has_fallback_exec;
207 return (bool)self->flags & dirstate_flag_has_fallback_exec;
194 }
208 }
195
209
196 static inline bool
210 static inline bool
197 dirstate_item_c_has_fallback_symlink(dirstateItemObject *self)
211 dirstate_item_c_has_fallback_symlink(dirstateItemObject *self)
198 {
212 {
199 return (bool)self->flags & dirstate_flag_has_fallback_symlink;
213 return (bool)self->flags & dirstate_flag_has_fallback_symlink;
200 }
214 }
201
215
202 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
216 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
203 {
217 {
204 if (self->flags & dirstate_flag_has_meaningful_data) {
218 if (self->flags & dirstate_flag_has_meaningful_data) {
205 return self->mode;
219 return self->mode;
206 } else {
220 } else {
207 return 0;
221 return 0;
208 }
222 }
209 }
223 }
210
224
211 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
225 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
212 {
226 {
213 if (!(self->flags & dirstate_flag_wc_tracked) &&
227 if (!(self->flags & dirstate_flag_wc_tracked) &&
214 (self->flags & dirstate_flag_p2_info)) {
228 (self->flags & dirstate_flag_p2_info)) {
215 if (self->flags & dirstate_flag_p1_tracked) {
229 if (self->flags & dirstate_flag_p1_tracked) {
216 return dirstate_v1_nonnormal;
230 return dirstate_v1_nonnormal;
217 } else {
231 } else {
218 return dirstate_v1_from_p2;
232 return dirstate_v1_from_p2;
219 }
233 }
220 } else if (dirstate_item_c_removed(self)) {
234 } else if (dirstate_item_c_removed(self)) {
221 return 0;
235 return 0;
222 } else if (self->flags & dirstate_flag_p2_info) {
236 } else if (self->flags & dirstate_flag_p2_info) {
223 return dirstate_v1_from_p2;
237 return dirstate_v1_from_p2;
224 } else if (dirstate_item_c_added(self)) {
238 } else if (dirstate_item_c_added(self)) {
225 return dirstate_v1_nonnormal;
239 return dirstate_v1_nonnormal;
226 } else if (self->flags & dirstate_flag_has_meaningful_data) {
240 } else if (self->flags & dirstate_flag_has_meaningful_data) {
227 return self->size;
241 return self->size;
228 } else {
242 } else {
229 return dirstate_v1_nonnormal;
243 return dirstate_v1_nonnormal;
230 }
244 }
231 }
245 }
232
246
233 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
247 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
234 {
248 {
235 if (dirstate_item_c_removed(self)) {
249 if (dirstate_item_c_removed(self)) {
236 return 0;
250 return 0;
237 } else if (!(self->flags & dirstate_flag_has_file_mtime) ||
251 } else if (!(self->flags & dirstate_flag_has_file_mtime) ||
238 !(self->flags & dirstate_flag_p1_tracked) ||
252 !(self->flags & dirstate_flag_p1_tracked) ||
239 !(self->flags & dirstate_flag_wc_tracked) ||
253 !(self->flags & dirstate_flag_wc_tracked) ||
240 (self->flags & dirstate_flag_p2_info)) {
254 (self->flags & dirstate_flag_p2_info)) {
241 return ambiguous_time;
255 return ambiguous_time;
242 } else {
256 } else {
243 return self->mtime;
257 return self->mtime;
244 }
258 }
245 }
259 }
246
260
247 static PyObject *dirstate_item_v2_data(dirstateItemObject *self)
261 static PyObject *dirstate_item_v2_data(dirstateItemObject *self)
248 {
262 {
249 int flags = self->flags;
263 int flags = self->flags;
250 int mode = dirstate_item_c_v1_mode(self);
264 int mode = dirstate_item_c_v1_mode(self);
251 if ((mode & S_IXUSR) != 0) {
265 if ((mode & S_IXUSR) != 0) {
252 flags |= dirstate_flag_mode_exec_perm;
266 flags |= dirstate_flag_mode_exec_perm;
253 } else {
267 } else {
254 flags &= ~dirstate_flag_mode_exec_perm;
268 flags &= ~dirstate_flag_mode_exec_perm;
255 }
269 }
256 if (S_ISLNK(mode)) {
270 if (S_ISLNK(mode)) {
257 flags |= dirstate_flag_mode_is_symlink;
271 flags |= dirstate_flag_mode_is_symlink;
258 } else {
272 } else {
259 flags &= ~dirstate_flag_mode_is_symlink;
273 flags &= ~dirstate_flag_mode_is_symlink;
260 }
274 }
261 return Py_BuildValue("iii", flags, self->size, self->mtime);
275 return Py_BuildValue("iii", flags, self->size, self->mtime);
262 };
276 };
263
277
264 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
278 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
265 {
279 {
266 char state = dirstate_item_c_v1_state(self);
280 char state = dirstate_item_c_v1_state(self);
267 return PyBytes_FromStringAndSize(&state, 1);
281 return PyBytes_FromStringAndSize(&state, 1);
268 };
282 };
269
283
270 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
284 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
271 {
285 {
272 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
286 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
273 };
287 };
274
288
275 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
289 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
276 {
290 {
277 return PyInt_FromLong(dirstate_item_c_v1_size(self));
291 return PyInt_FromLong(dirstate_item_c_v1_size(self));
278 };
292 };
279
293
280 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
294 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
281 {
295 {
282 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
296 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
283 };
297 };
284
298
285 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
299 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
286 PyObject *value)
300 PyObject *value)
287 {
301 {
288 long now;
302 long now;
289 if (!pylong_to_long(value, &now)) {
303 if (!pylong_to_long(value, &now)) {
290 return NULL;
304 return NULL;
291 }
305 }
292 if (dirstate_item_c_v1_state(self) == 'n' &&
306 if (dirstate_item_c_v1_state(self) == 'n' &&
293 dirstate_item_c_v1_mtime(self) == now) {
307 dirstate_item_c_v1_mtime(self) == now) {
294 Py_RETURN_TRUE;
308 Py_RETURN_TRUE;
295 } else {
309 } else {
296 Py_RETURN_FALSE;
310 Py_RETURN_FALSE;
297 }
311 }
298 };
312 };
299
313
300 /* This will never change since it's bound to V1
314 /* This will never change since it's bound to V1
301 */
315 */
302 static inline dirstateItemObject *
316 static inline dirstateItemObject *
303 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
317 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
304 {
318 {
305 dirstateItemObject *t =
319 dirstateItemObject *t =
306 PyObject_New(dirstateItemObject, &dirstateItemType);
320 PyObject_New(dirstateItemObject, &dirstateItemType);
307 if (!t) {
321 if (!t) {
308 return NULL;
322 return NULL;
309 }
323 }
310 t->flags = 0;
324 t->flags = 0;
311 t->mode = 0;
325 t->mode = 0;
312 t->size = 0;
326 t->size = 0;
313 t->mtime = 0;
327 t->mtime = 0;
314
328
315 if (state == 'm') {
329 if (state == 'm') {
316 t->flags = (dirstate_flag_wc_tracked |
330 t->flags = (dirstate_flag_wc_tracked |
317 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
331 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
318 } else if (state == 'a') {
332 } else if (state == 'a') {
319 t->flags = dirstate_flag_wc_tracked;
333 t->flags = dirstate_flag_wc_tracked;
320 } else if (state == 'r') {
334 } else if (state == 'r') {
321 if (size == dirstate_v1_nonnormal) {
335 if (size == dirstate_v1_nonnormal) {
322 t->flags =
336 t->flags =
323 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
337 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
324 } else if (size == dirstate_v1_from_p2) {
338 } else if (size == dirstate_v1_from_p2) {
325 t->flags = dirstate_flag_p2_info;
339 t->flags = dirstate_flag_p2_info;
326 } else {
340 } else {
327 t->flags = dirstate_flag_p1_tracked;
341 t->flags = dirstate_flag_p1_tracked;
328 }
342 }
329 } else if (state == 'n') {
343 } else if (state == 'n') {
330 if (size == dirstate_v1_from_p2) {
344 if (size == dirstate_v1_from_p2) {
331 t->flags =
345 t->flags =
332 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
346 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
333 } else if (size == dirstate_v1_nonnormal) {
347 } else if (size == dirstate_v1_nonnormal) {
334 t->flags =
348 t->flags =
335 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
349 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
336 } else if (mtime == ambiguous_time) {
350 } else if (mtime == ambiguous_time) {
337 t->flags = (dirstate_flag_wc_tracked |
351 t->flags = (dirstate_flag_wc_tracked |
338 dirstate_flag_p1_tracked |
352 dirstate_flag_p1_tracked |
339 dirstate_flag_has_meaningful_data);
353 dirstate_flag_has_meaningful_data);
340 t->mode = mode;
354 t->mode = mode;
341 t->size = size;
355 t->size = size;
342 } else {
356 } else {
343 t->flags = (dirstate_flag_wc_tracked |
357 t->flags = (dirstate_flag_wc_tracked |
344 dirstate_flag_p1_tracked |
358 dirstate_flag_p1_tracked |
345 dirstate_flag_has_meaningful_data |
359 dirstate_flag_has_meaningful_data |
346 dirstate_flag_has_file_mtime);
360 dirstate_flag_has_file_mtime);
347 t->mode = mode;
361 t->mode = mode;
348 t->size = size;
362 t->size = size;
349 t->mtime = mtime;
363 t->mtime = mtime;
350 }
364 }
351 } else {
365 } else {
352 PyErr_Format(PyExc_RuntimeError,
366 PyErr_Format(PyExc_RuntimeError,
353 "unknown state: `%c` (%d, %d, %d)", state, mode,
367 "unknown state: `%c` (%d, %d, %d)", state, mode,
354 size, mtime, NULL);
368 size, mtime, NULL);
355 Py_DECREF(t);
369 Py_DECREF(t);
356 return NULL;
370 return NULL;
357 }
371 }
358
372
359 return t;
373 return t;
360 }
374 }
361
375
362 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
376 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
363 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
377 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
364 PyObject *args)
378 PyObject *args)
365 {
379 {
366 /* We do all the initialization here and not a tp_init function because
380 /* We do all the initialization here and not a tp_init function because
367 * dirstate_item is immutable. */
381 * dirstate_item is immutable. */
368 char state;
382 char state;
369 int size, mode, mtime;
383 int size, mode, mtime;
370 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
384 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
371 return NULL;
385 return NULL;
372 }
386 }
373 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
387 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
374 };
388 };
375
389
376 static PyObject *dirstate_item_from_v2_meth(PyTypeObject *subtype,
390 static PyObject *dirstate_item_from_v2_meth(PyTypeObject *subtype,
377 PyObject *args)
391 PyObject *args)
378 {
392 {
379 dirstateItemObject *t =
393 dirstateItemObject *t =
380 PyObject_New(dirstateItemObject, &dirstateItemType);
394 PyObject_New(dirstateItemObject, &dirstateItemType);
381 if (!t) {
395 if (!t) {
382 return NULL;
396 return NULL;
383 }
397 }
384 if (!PyArg_ParseTuple(args, "iii", &t->flags, &t->size, &t->mtime)) {
398 if (!PyArg_ParseTuple(args, "iii", &t->flags, &t->size, &t->mtime)) {
385 return NULL;
399 return NULL;
386 }
400 }
387 if (t->flags & dirstate_flag_expected_state_is_modified) {
401 if (t->flags & dirstate_flag_expected_state_is_modified) {
388 t->flags &= ~(dirstate_flag_expected_state_is_modified |
402 t->flags &= ~(dirstate_flag_expected_state_is_modified |
389 dirstate_flag_has_meaningful_data |
403 dirstate_flag_has_meaningful_data |
390 dirstate_flag_has_file_mtime);
404 dirstate_flag_has_file_mtime);
391 }
405 }
392 t->mode = 0;
406 t->mode = 0;
393 if (t->flags & dirstate_flag_has_meaningful_data) {
407 if (t->flags & dirstate_flag_has_meaningful_data) {
394 if (t->flags & dirstate_flag_mode_exec_perm) {
408 if (t->flags & dirstate_flag_mode_exec_perm) {
395 t->mode = 0755;
409 t->mode = 0755;
396 } else {
410 } else {
397 t->mode = 0644;
411 t->mode = 0644;
398 }
412 }
399 if (t->flags & dirstate_flag_mode_is_symlink) {
413 if (t->flags & dirstate_flag_mode_is_symlink) {
400 t->mode |= S_IFLNK;
414 t->mode |= S_IFLNK;
401 } else {
415 } else {
402 t->mode |= S_IFREG;
416 t->mode |= S_IFREG;
403 }
417 }
404 }
418 }
405 return (PyObject *)t;
419 return (PyObject *)t;
406 };
420 };
407
421
408 /* This means the next status call will have to actually check its content
422 /* This means the next status call will have to actually check its content
409 to make sure it is correct. */
423 to make sure it is correct. */
410 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
424 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
411 {
425 {
412 self->flags &= ~dirstate_flag_has_file_mtime;
426 self->flags &= ~dirstate_flag_has_file_mtime;
413 Py_RETURN_NONE;
427 Py_RETURN_NONE;
414 }
428 }
415
429
416 /* See docstring of the python implementation for details */
430 /* See docstring of the python implementation for details */
417 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
431 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
418 PyObject *args)
432 PyObject *args)
419 {
433 {
420 int size, mode, mtime;
434 int size, mode, mtime;
421 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
435 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
422 return NULL;
436 return NULL;
423 }
437 }
424 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
438 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
425 dirstate_flag_has_meaningful_data |
439 dirstate_flag_has_meaningful_data |
426 dirstate_flag_has_file_mtime;
440 dirstate_flag_has_file_mtime;
427 self->mode = mode;
441 self->mode = mode;
428 self->size = size;
442 self->size = size;
429 self->mtime = mtime;
443 self->mtime = mtime;
430 Py_RETURN_NONE;
444 Py_RETURN_NONE;
431 }
445 }
432
446
433 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
447 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
434 {
448 {
435 self->flags |= dirstate_flag_wc_tracked;
449 self->flags |= dirstate_flag_wc_tracked;
436 self->flags &= ~dirstate_flag_has_file_mtime;
450 self->flags &= ~dirstate_flag_has_file_mtime;
437 Py_RETURN_NONE;
451 Py_RETURN_NONE;
438 }
452 }
439
453
440 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
454 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
441 {
455 {
442 self->flags &= ~dirstate_flag_wc_tracked;
456 self->flags &= ~dirstate_flag_wc_tracked;
443 self->mode = 0;
457 self->mode = 0;
444 self->mtime = 0;
458 self->mtime = 0;
445 self->size = 0;
459 self->size = 0;
446 Py_RETURN_NONE;
460 Py_RETURN_NONE;
447 }
461 }
448
462
449 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
463 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
450 {
464 {
451 if (self->flags & dirstate_flag_p2_info) {
465 if (self->flags & dirstate_flag_p2_info) {
452 self->flags &= ~(dirstate_flag_p2_info |
466 self->flags &= ~(dirstate_flag_p2_info |
453 dirstate_flag_has_meaningful_data |
467 dirstate_flag_has_meaningful_data |
454 dirstate_flag_has_file_mtime);
468 dirstate_flag_has_file_mtime);
455 self->mode = 0;
469 self->mode = 0;
456 self->mtime = 0;
470 self->mtime = 0;
457 self->size = 0;
471 self->size = 0;
458 }
472 }
459 Py_RETURN_NONE;
473 Py_RETURN_NONE;
460 }
474 }
461 static PyMethodDef dirstate_item_methods[] = {
475 static PyMethodDef dirstate_item_methods[] = {
462 {"v2_data", (PyCFunction)dirstate_item_v2_data, METH_NOARGS,
476 {"v2_data", (PyCFunction)dirstate_item_v2_data, METH_NOARGS,
463 "return data suitable for v2 serialization"},
477 "return data suitable for v2 serialization"},
464 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
478 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
465 "return a \"state\" suitable for v1 serialization"},
479 "return a \"state\" suitable for v1 serialization"},
466 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
480 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
467 "return a \"mode\" suitable for v1 serialization"},
481 "return a \"mode\" suitable for v1 serialization"},
468 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
482 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
469 "return a \"size\" suitable for v1 serialization"},
483 "return a \"size\" suitable for v1 serialization"},
470 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
484 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
471 "return a \"mtime\" suitable for v1 serialization"},
485 "return a \"mtime\" suitable for v1 serialization"},
472 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
486 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
473 "True if the stored mtime would be ambiguous with the current time"},
487 "True if the stored mtime would be ambiguous with the current time"},
474 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
488 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
475 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
489 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
476 {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth,
490 {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth,
477 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V2 data"},
491 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V2 data"},
478 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
492 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
479 METH_NOARGS, "mark a file as \"possibly dirty\""},
493 METH_NOARGS, "mark a file as \"possibly dirty\""},
480 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
494 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
481 "mark a file as \"clean\""},
495 "mark a file as \"clean\""},
482 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
496 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
483 "mark a file as \"tracked\""},
497 "mark a file as \"tracked\""},
484 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
498 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
485 "mark a file as \"untracked\""},
499 "mark a file as \"untracked\""},
486 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
500 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
487 "remove all \"merge-only\" from a DirstateItem"},
501 "remove all \"merge-only\" from a DirstateItem"},
488 {NULL} /* Sentinel */
502 {NULL} /* Sentinel */
489 };
503 };
490
504
491 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
505 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
492 {
506 {
493 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
507 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
494 };
508 };
495
509
496 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
510 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
497 {
511 {
498 return PyInt_FromLong(dirstate_item_c_v1_size(self));
512 return PyInt_FromLong(dirstate_item_c_v1_size(self));
499 };
513 };
500
514
501 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
515 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
502 {
516 {
503 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
517 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
504 };
518 };
505
519
506 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
520 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
507 {
521 {
508 char state = dirstate_item_c_v1_state(self);
522 char state = dirstate_item_c_v1_state(self);
509 return PyBytes_FromStringAndSize(&state, 1);
523 return PyBytes_FromStringAndSize(&state, 1);
510 };
524 };
511
525
512 static PyObject *dirstate_item_get_has_fallback_exec(dirstateItemObject *self)
526 static PyObject *dirstate_item_get_has_fallback_exec(dirstateItemObject *self)
513 {
527 {
514 if (dirstate_item_c_has_fallback_exec(self)) {
528 if (dirstate_item_c_has_fallback_exec(self)) {
515 Py_RETURN_TRUE;
529 Py_RETURN_TRUE;
516 } else {
530 } else {
517 Py_RETURN_FALSE;
531 Py_RETURN_FALSE;
518 }
532 }
519 };
533 };
520
534
521 static PyObject *dirstate_item_get_fallback_exec(dirstateItemObject *self)
535 static PyObject *dirstate_item_get_fallback_exec(dirstateItemObject *self)
522 {
536 {
523 if (dirstate_item_c_has_fallback_exec(self)) {
537 if (dirstate_item_c_has_fallback_exec(self)) {
524 if (self->flags & dirstate_flag_fallback_exec) {
538 if (self->flags & dirstate_flag_fallback_exec) {
525 Py_RETURN_TRUE;
539 Py_RETURN_TRUE;
526 } else {
540 } else {
527 Py_RETURN_FALSE;
541 Py_RETURN_FALSE;
528 }
542 }
529 } else {
543 } else {
530 Py_RETURN_NONE;
544 Py_RETURN_NONE;
531 }
545 }
532 };
546 };
533
547
534 static int dirstate_item_set_fallback_exec(dirstateItemObject *self,
548 static int dirstate_item_set_fallback_exec(dirstateItemObject *self,
535 PyObject *value)
549 PyObject *value)
536 {
550 {
537 if ((value == Py_None) || (value == NULL)) {
551 if ((value == Py_None) || (value == NULL)) {
538 self->flags &= ~dirstate_flag_has_fallback_exec;
552 self->flags &= ~dirstate_flag_has_fallback_exec;
539 } else {
553 } else {
540 self->flags |= dirstate_flag_has_fallback_exec;
554 self->flags |= dirstate_flag_has_fallback_exec;
541 if (PyObject_IsTrue(value)) {
555 if (PyObject_IsTrue(value)) {
542 self->flags |= dirstate_flag_fallback_exec;
556 self->flags |= dirstate_flag_fallback_exec;
543 } else {
557 } else {
544 self->flags &= ~dirstate_flag_fallback_exec;
558 self->flags &= ~dirstate_flag_fallback_exec;
545 }
559 }
546 }
560 }
547 return 0;
561 return 0;
548 };
562 };
549
563
550 static PyObject *
564 static PyObject *
551 dirstate_item_get_has_fallback_symlink(dirstateItemObject *self)
565 dirstate_item_get_has_fallback_symlink(dirstateItemObject *self)
552 {
566 {
553 if (dirstate_item_c_has_fallback_symlink(self)) {
567 if (dirstate_item_c_has_fallback_symlink(self)) {
554 Py_RETURN_TRUE;
568 Py_RETURN_TRUE;
555 } else {
569 } else {
556 Py_RETURN_FALSE;
570 Py_RETURN_FALSE;
557 }
571 }
558 };
572 };
559
573
560 static PyObject *dirstate_item_get_fallback_symlink(dirstateItemObject *self)
574 static PyObject *dirstate_item_get_fallback_symlink(dirstateItemObject *self)
561 {
575 {
562 if (dirstate_item_c_has_fallback_symlink(self)) {
576 if (dirstate_item_c_has_fallback_symlink(self)) {
563 if (self->flags & dirstate_flag_fallback_symlink) {
577 if (self->flags & dirstate_flag_fallback_symlink) {
564 Py_RETURN_TRUE;
578 Py_RETURN_TRUE;
565 } else {
579 } else {
566 Py_RETURN_FALSE;
580 Py_RETURN_FALSE;
567 }
581 }
568 } else {
582 } else {
569 Py_RETURN_NONE;
583 Py_RETURN_NONE;
570 }
584 }
571 };
585 };
572
586
573 static int dirstate_item_set_fallback_symlink(dirstateItemObject *self,
587 static int dirstate_item_set_fallback_symlink(dirstateItemObject *self,
574 PyObject *value)
588 PyObject *value)
575 {
589 {
576 if ((value == Py_None) || (value == NULL)) {
590 if ((value == Py_None) || (value == NULL)) {
577 self->flags &= ~dirstate_flag_has_fallback_symlink;
591 self->flags &= ~dirstate_flag_has_fallback_symlink;
578 } else {
592 } else {
579 self->flags |= dirstate_flag_has_fallback_symlink;
593 self->flags |= dirstate_flag_has_fallback_symlink;
580 if (PyObject_IsTrue(value)) {
594 if (PyObject_IsTrue(value)) {
581 self->flags |= dirstate_flag_fallback_symlink;
595 self->flags |= dirstate_flag_fallback_symlink;
582 } else {
596 } else {
583 self->flags &= ~dirstate_flag_fallback_symlink;
597 self->flags &= ~dirstate_flag_fallback_symlink;
584 }
598 }
585 }
599 }
586 return 0;
600 return 0;
587 };
601 };
588
602
589 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
603 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
590 {
604 {
591 if (dirstate_item_c_tracked(self)) {
605 if (dirstate_item_c_tracked(self)) {
592 Py_RETURN_TRUE;
606 Py_RETURN_TRUE;
593 } else {
607 } else {
594 Py_RETURN_FALSE;
608 Py_RETURN_FALSE;
595 }
609 }
596 };
610 };
597 static PyObject *dirstate_item_get_p1_tracked(dirstateItemObject *self)
611 static PyObject *dirstate_item_get_p1_tracked(dirstateItemObject *self)
598 {
612 {
599 if (self->flags & dirstate_flag_p1_tracked) {
613 if (self->flags & dirstate_flag_p1_tracked) {
600 Py_RETURN_TRUE;
614 Py_RETURN_TRUE;
601 } else {
615 } else {
602 Py_RETURN_FALSE;
616 Py_RETURN_FALSE;
603 }
617 }
604 };
618 };
605
619
606 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
620 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
607 {
621 {
608 if (dirstate_item_c_added(self)) {
622 if (dirstate_item_c_added(self)) {
609 Py_RETURN_TRUE;
623 Py_RETURN_TRUE;
610 } else {
624 } else {
611 Py_RETURN_FALSE;
625 Py_RETURN_FALSE;
612 }
626 }
613 };
627 };
614
628
615 static PyObject *dirstate_item_get_p2_info(dirstateItemObject *self)
629 static PyObject *dirstate_item_get_p2_info(dirstateItemObject *self)
616 {
630 {
617 if (self->flags & dirstate_flag_wc_tracked &&
631 if (self->flags & dirstate_flag_wc_tracked &&
618 self->flags & dirstate_flag_p2_info) {
632 self->flags & dirstate_flag_p2_info) {
619 Py_RETURN_TRUE;
633 Py_RETURN_TRUE;
620 } else {
634 } else {
621 Py_RETURN_FALSE;
635 Py_RETURN_FALSE;
622 }
636 }
623 };
637 };
624
638
625 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
639 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
626 {
640 {
627 if (dirstate_item_c_merged(self)) {
641 if (dirstate_item_c_merged(self)) {
628 Py_RETURN_TRUE;
642 Py_RETURN_TRUE;
629 } else {
643 } else {
630 Py_RETURN_FALSE;
644 Py_RETURN_FALSE;
631 }
645 }
632 };
646 };
633
647
634 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
648 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
635 {
649 {
636 if (dirstate_item_c_from_p2(self)) {
650 if (dirstate_item_c_from_p2(self)) {
637 Py_RETURN_TRUE;
651 Py_RETURN_TRUE;
638 } else {
652 } else {
639 Py_RETURN_FALSE;
653 Py_RETURN_FALSE;
640 }
654 }
641 };
655 };
642
656
643 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
657 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
644 {
658 {
645 if (!(self->flags & dirstate_flag_wc_tracked)) {
659 if (!(self->flags & dirstate_flag_wc_tracked)) {
646 Py_RETURN_FALSE;
660 Py_RETURN_FALSE;
647 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
661 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
648 Py_RETURN_FALSE;
662 Py_RETURN_FALSE;
649 } else if (self->flags & dirstate_flag_p2_info) {
663 } else if (self->flags & dirstate_flag_p2_info) {
650 Py_RETURN_FALSE;
664 Py_RETURN_FALSE;
651 } else {
665 } else {
652 Py_RETURN_TRUE;
666 Py_RETURN_TRUE;
653 }
667 }
654 };
668 };
655
669
656 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
670 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
657 {
671 {
658 if (dirstate_item_c_any_tracked(self)) {
672 if (dirstate_item_c_any_tracked(self)) {
659 Py_RETURN_TRUE;
673 Py_RETURN_TRUE;
660 } else {
674 } else {
661 Py_RETURN_FALSE;
675 Py_RETURN_FALSE;
662 }
676 }
663 };
677 };
664
678
665 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
679 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
666 {
680 {
667 if (dirstate_item_c_removed(self)) {
681 if (dirstate_item_c_removed(self)) {
668 Py_RETURN_TRUE;
682 Py_RETURN_TRUE;
669 } else {
683 } else {
670 Py_RETURN_FALSE;
684 Py_RETURN_FALSE;
671 }
685 }
672 };
686 };
673
687
674 static PyGetSetDef dirstate_item_getset[] = {
688 static PyGetSetDef dirstate_item_getset[] = {
675 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
689 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
676 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
690 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
677 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
691 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
678 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
692 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
679 {"has_fallback_exec", (getter)dirstate_item_get_has_fallback_exec, NULL,
693 {"has_fallback_exec", (getter)dirstate_item_get_has_fallback_exec, NULL,
680 "has_fallback_exec", NULL},
694 "has_fallback_exec", NULL},
681 {"fallback_exec", (getter)dirstate_item_get_fallback_exec,
695 {"fallback_exec", (getter)dirstate_item_get_fallback_exec,
682 (setter)dirstate_item_set_fallback_exec, "fallback_exec", NULL},
696 (setter)dirstate_item_set_fallback_exec, "fallback_exec", NULL},
683 {"has_fallback_symlink", (getter)dirstate_item_get_has_fallback_symlink,
697 {"has_fallback_symlink", (getter)dirstate_item_get_has_fallback_symlink,
684 NULL, "has_fallback_symlink", NULL},
698 NULL, "has_fallback_symlink", NULL},
685 {"fallback_symlink", (getter)dirstate_item_get_fallback_symlink,
699 {"fallback_symlink", (getter)dirstate_item_get_fallback_symlink,
686 (setter)dirstate_item_set_fallback_symlink, "fallback_symlink", NULL},
700 (setter)dirstate_item_set_fallback_symlink, "fallback_symlink", NULL},
687 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
701 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
688 {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
702 {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
689 NULL},
703 NULL},
690 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
704 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
691 {"p2_info", (getter)dirstate_item_get_p2_info, NULL, "p2_info", NULL},
705 {"p2_info", (getter)dirstate_item_get_p2_info, NULL, "p2_info", NULL},
692 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
706 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
693 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
707 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
694 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
708 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
695 NULL},
709 NULL},
696 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
710 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
697 NULL},
711 NULL},
698 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
712 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
699 {NULL} /* Sentinel */
713 {NULL} /* Sentinel */
700 };
714 };
701
715
702 PyTypeObject dirstateItemType = {
716 PyTypeObject dirstateItemType = {
703 PyVarObject_HEAD_INIT(NULL, 0) /* header */
717 PyVarObject_HEAD_INIT(NULL, 0) /* header */
704 "dirstate_tuple", /* tp_name */
718 "dirstate_tuple", /* tp_name */
705 sizeof(dirstateItemObject), /* tp_basicsize */
719 sizeof(dirstateItemObject), /* tp_basicsize */
706 0, /* tp_itemsize */
720 0, /* tp_itemsize */
707 (destructor)dirstate_item_dealloc, /* tp_dealloc */
721 (destructor)dirstate_item_dealloc, /* tp_dealloc */
708 0, /* tp_print */
722 0, /* tp_print */
709 0, /* tp_getattr */
723 0, /* tp_getattr */
710 0, /* tp_setattr */
724 0, /* tp_setattr */
711 0, /* tp_compare */
725 0, /* tp_compare */
712 0, /* tp_repr */
726 0, /* tp_repr */
713 0, /* tp_as_number */
727 0, /* tp_as_number */
714 0, /* tp_as_sequence */
728 0, /* tp_as_sequence */
715 0, /* tp_as_mapping */
729 0, /* tp_as_mapping */
716 0, /* tp_hash */
730 0, /* tp_hash */
717 0, /* tp_call */
731 0, /* tp_call */
718 0, /* tp_str */
732 0, /* tp_str */
719 0, /* tp_getattro */
733 0, /* tp_getattro */
720 0, /* tp_setattro */
734 0, /* tp_setattro */
721 0, /* tp_as_buffer */
735 0, /* tp_as_buffer */
722 Py_TPFLAGS_DEFAULT, /* tp_flags */
736 Py_TPFLAGS_DEFAULT, /* tp_flags */
723 "dirstate tuple", /* tp_doc */
737 "dirstate tuple", /* tp_doc */
724 0, /* tp_traverse */
738 0, /* tp_traverse */
725 0, /* tp_clear */
739 0, /* tp_clear */
726 0, /* tp_richcompare */
740 0, /* tp_richcompare */
727 0, /* tp_weaklistoffset */
741 0, /* tp_weaklistoffset */
728 0, /* tp_iter */
742 0, /* tp_iter */
729 0, /* tp_iternext */
743 0, /* tp_iternext */
730 dirstate_item_methods, /* tp_methods */
744 dirstate_item_methods, /* tp_methods */
731 0, /* tp_members */
745 0, /* tp_members */
732 dirstate_item_getset, /* tp_getset */
746 dirstate_item_getset, /* tp_getset */
733 0, /* tp_base */
747 0, /* tp_base */
734 0, /* tp_dict */
748 0, /* tp_dict */
735 0, /* tp_descr_get */
749 0, /* tp_descr_get */
736 0, /* tp_descr_set */
750 0, /* tp_descr_set */
737 0, /* tp_dictoffset */
751 0, /* tp_dictoffset */
738 0, /* tp_init */
752 0, /* tp_init */
739 0, /* tp_alloc */
753 0, /* tp_alloc */
740 dirstate_item_new, /* tp_new */
754 dirstate_item_new, /* tp_new */
741 };
755 };
742
756
743 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
757 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
744 {
758 {
745 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
759 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
746 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
760 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
747 char state, *cur, *str, *cpos;
761 char state, *cur, *str, *cpos;
748 int mode, size, mtime;
762 int mode, size, mtime;
749 unsigned int flen, pos = 40;
763 unsigned int flen, pos = 40;
750 Py_ssize_t len = 40;
764 Py_ssize_t len = 40;
751 Py_ssize_t readlen;
765 Py_ssize_t readlen;
752
766
753 if (!PyArg_ParseTuple(
767 if (!PyArg_ParseTuple(
754 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
768 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
755 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
769 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
756 goto quit;
770 goto quit;
757 }
771 }
758
772
759 len = readlen;
773 len = readlen;
760
774
761 /* read parents */
775 /* read parents */
762 if (len < 40) {
776 if (len < 40) {
763 PyErr_SetString(PyExc_ValueError,
777 PyErr_SetString(PyExc_ValueError,
764 "too little data for parents");
778 "too little data for parents");
765 goto quit;
779 goto quit;
766 }
780 }
767
781
768 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
782 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
769 str + 20, (Py_ssize_t)20);
783 str + 20, (Py_ssize_t)20);
770 if (!parents) {
784 if (!parents) {
771 goto quit;
785 goto quit;
772 }
786 }
773
787
774 /* read filenames */
788 /* read filenames */
775 while (pos >= 40 && pos < len) {
789 while (pos >= 40 && pos < len) {
776 if (pos + 17 > len) {
790 if (pos + 17 > len) {
777 PyErr_SetString(PyExc_ValueError,
791 PyErr_SetString(PyExc_ValueError,
778 "overflow in dirstate");
792 "overflow in dirstate");
779 goto quit;
793 goto quit;
780 }
794 }
781 cur = str + pos;
795 cur = str + pos;
782 /* unpack header */
796 /* unpack header */
783 state = *cur;
797 state = *cur;
784 mode = getbe32(cur + 1);
798 mode = getbe32(cur + 1);
785 size = getbe32(cur + 5);
799 size = getbe32(cur + 5);
786 mtime = getbe32(cur + 9);
800 mtime = getbe32(cur + 9);
787 flen = getbe32(cur + 13);
801 flen = getbe32(cur + 13);
788 pos += 17;
802 pos += 17;
789 cur += 17;
803 cur += 17;
790 if (flen > len - pos) {
804 if (flen > len - pos) {
791 PyErr_SetString(PyExc_ValueError,
805 PyErr_SetString(PyExc_ValueError,
792 "overflow in dirstate");
806 "overflow in dirstate");
793 goto quit;
807 goto quit;
794 }
808 }
795
809
796 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
810 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
797 size, mtime);
811 size, mtime);
798 if (!entry)
812 if (!entry)
799 goto quit;
813 goto quit;
800 cpos = memchr(cur, 0, flen);
814 cpos = memchr(cur, 0, flen);
801 if (cpos) {
815 if (cpos) {
802 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
816 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
803 cname = PyBytes_FromStringAndSize(
817 cname = PyBytes_FromStringAndSize(
804 cpos + 1, flen - (cpos - cur) - 1);
818 cpos + 1, flen - (cpos - cur) - 1);
805 if (!fname || !cname ||
819 if (!fname || !cname ||
806 PyDict_SetItem(cmap, fname, cname) == -1 ||
820 PyDict_SetItem(cmap, fname, cname) == -1 ||
807 PyDict_SetItem(dmap, fname, entry) == -1) {
821 PyDict_SetItem(dmap, fname, entry) == -1) {
808 goto quit;
822 goto quit;
809 }
823 }
810 Py_DECREF(cname);
824 Py_DECREF(cname);
811 } else {
825 } else {
812 fname = PyBytes_FromStringAndSize(cur, flen);
826 fname = PyBytes_FromStringAndSize(cur, flen);
813 if (!fname ||
827 if (!fname ||
814 PyDict_SetItem(dmap, fname, entry) == -1) {
828 PyDict_SetItem(dmap, fname, entry) == -1) {
815 goto quit;
829 goto quit;
816 }
830 }
817 }
831 }
818 Py_DECREF(fname);
832 Py_DECREF(fname);
819 Py_DECREF(entry);
833 Py_DECREF(entry);
820 fname = cname = entry = NULL;
834 fname = cname = entry = NULL;
821 pos += flen;
835 pos += flen;
822 }
836 }
823
837
824 ret = parents;
838 ret = parents;
825 Py_INCREF(ret);
839 Py_INCREF(ret);
826 quit:
840 quit:
827 Py_XDECREF(fname);
841 Py_XDECREF(fname);
828 Py_XDECREF(cname);
842 Py_XDECREF(cname);
829 Py_XDECREF(entry);
843 Py_XDECREF(entry);
830 Py_XDECREF(parents);
844 Py_XDECREF(parents);
831 return ret;
845 return ret;
832 }
846 }
833
847
834 /*
848 /*
835 * Efficiently pack a dirstate object into its on-disk format.
849 * Efficiently pack a dirstate object into its on-disk format.
836 */
850 */
837 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
851 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
838 {
852 {
839 PyObject *packobj = NULL;
853 PyObject *packobj = NULL;
840 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
854 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
841 Py_ssize_t nbytes, pos, l;
855 Py_ssize_t nbytes, pos, l;
842 PyObject *k, *v = NULL, *pn;
856 PyObject *k, *v = NULL, *pn;
843 char *p, *s;
857 char *p, *s;
844 int now;
858 int now;
845
859
846 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
860 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
847 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
861 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
848 &now)) {
862 &now)) {
849 return NULL;
863 return NULL;
850 }
864 }
851
865
852 if (PyTuple_Size(pl) != 2) {
866 if (PyTuple_Size(pl) != 2) {
853 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
867 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
854 return NULL;
868 return NULL;
855 }
869 }
856
870
857 /* Figure out how much we need to allocate. */
871 /* Figure out how much we need to allocate. */
858 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
872 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
859 PyObject *c;
873 PyObject *c;
860 if (!PyBytes_Check(k)) {
874 if (!PyBytes_Check(k)) {
861 PyErr_SetString(PyExc_TypeError, "expected string key");
875 PyErr_SetString(PyExc_TypeError, "expected string key");
862 goto bail;
876 goto bail;
863 }
877 }
864 nbytes += PyBytes_GET_SIZE(k) + 17;
878 nbytes += PyBytes_GET_SIZE(k) + 17;
865 c = PyDict_GetItem(copymap, k);
879 c = PyDict_GetItem(copymap, k);
866 if (c) {
880 if (c) {
867 if (!PyBytes_Check(c)) {
881 if (!PyBytes_Check(c)) {
868 PyErr_SetString(PyExc_TypeError,
882 PyErr_SetString(PyExc_TypeError,
869 "expected string key");
883 "expected string key");
870 goto bail;
884 goto bail;
871 }
885 }
872 nbytes += PyBytes_GET_SIZE(c) + 1;
886 nbytes += PyBytes_GET_SIZE(c) + 1;
873 }
887 }
874 }
888 }
875
889
876 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
890 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
877 if (packobj == NULL) {
891 if (packobj == NULL) {
878 goto bail;
892 goto bail;
879 }
893 }
880
894
881 p = PyBytes_AS_STRING(packobj);
895 p = PyBytes_AS_STRING(packobj);
882
896
883 pn = PyTuple_GET_ITEM(pl, 0);
897 pn = PyTuple_GET_ITEM(pl, 0);
884 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
898 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
885 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
899 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
886 goto bail;
900 goto bail;
887 }
901 }
888 memcpy(p, s, l);
902 memcpy(p, s, l);
889 p += 20;
903 p += 20;
890 pn = PyTuple_GET_ITEM(pl, 1);
904 pn = PyTuple_GET_ITEM(pl, 1);
891 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
905 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
892 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
906 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
893 goto bail;
907 goto bail;
894 }
908 }
895 memcpy(p, s, l);
909 memcpy(p, s, l);
896 p += 20;
910 p += 20;
897
911
898 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
912 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
899 dirstateItemObject *tuple;
913 dirstateItemObject *tuple;
900 char state;
914 char state;
901 int mode, size, mtime;
915 int mode, size, mtime;
902 Py_ssize_t len, l;
916 Py_ssize_t len, l;
903 PyObject *o;
917 PyObject *o;
904 char *t;
918 char *t;
905
919
906 if (!dirstate_tuple_check(v)) {
920 if (!dirstate_tuple_check(v)) {
907 PyErr_SetString(PyExc_TypeError,
921 PyErr_SetString(PyExc_TypeError,
908 "expected a dirstate tuple");
922 "expected a dirstate tuple");
909 goto bail;
923 goto bail;
910 }
924 }
911 tuple = (dirstateItemObject *)v;
925 tuple = (dirstateItemObject *)v;
912
926
913 state = dirstate_item_c_v1_state(tuple);
927 state = dirstate_item_c_v1_state(tuple);
914 mode = dirstate_item_c_v1_mode(tuple);
928 mode = dirstate_item_c_v1_mode(tuple);
915 size = dirstate_item_c_v1_size(tuple);
929 size = dirstate_item_c_v1_size(tuple);
916 mtime = dirstate_item_c_v1_mtime(tuple);
930 mtime = dirstate_item_c_v1_mtime(tuple);
917 if (state == 'n' && mtime == now) {
931 if (state == 'n' && mtime == now) {
918 /* See pure/parsers.py:pack_dirstate for why we do
932 /* See pure/parsers.py:pack_dirstate for why we do
919 * this. */
933 * this. */
920 mtime = -1;
934 mtime = -1;
921 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
935 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
922 state, mode, size, mtime);
936 state, mode, size, mtime);
923 if (!mtime_unset) {
937 if (!mtime_unset) {
924 goto bail;
938 goto bail;
925 }
939 }
926 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
940 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
927 goto bail;
941 goto bail;
928 }
942 }
929 Py_DECREF(mtime_unset);
943 Py_DECREF(mtime_unset);
930 mtime_unset = NULL;
944 mtime_unset = NULL;
931 }
945 }
932 *p++ = state;
946 *p++ = state;
933 putbe32((uint32_t)mode, p);
947 putbe32((uint32_t)mode, p);
934 putbe32((uint32_t)size, p + 4);
948 putbe32((uint32_t)size, p + 4);
935 putbe32((uint32_t)mtime, p + 8);
949 putbe32((uint32_t)mtime, p + 8);
936 t = p + 12;
950 t = p + 12;
937 p += 16;
951 p += 16;
938 len = PyBytes_GET_SIZE(k);
952 len = PyBytes_GET_SIZE(k);
939 memcpy(p, PyBytes_AS_STRING(k), len);
953 memcpy(p, PyBytes_AS_STRING(k), len);
940 p += len;
954 p += len;
941 o = PyDict_GetItem(copymap, k);
955 o = PyDict_GetItem(copymap, k);
942 if (o) {
956 if (o) {
943 *p++ = '\0';
957 *p++ = '\0';
944 l = PyBytes_GET_SIZE(o);
958 l = PyBytes_GET_SIZE(o);
945 memcpy(p, PyBytes_AS_STRING(o), l);
959 memcpy(p, PyBytes_AS_STRING(o), l);
946 p += l;
960 p += l;
947 len += l + 1;
961 len += l + 1;
948 }
962 }
949 putbe32((uint32_t)len, t);
963 putbe32((uint32_t)len, t);
950 }
964 }
951
965
952 pos = p - PyBytes_AS_STRING(packobj);
966 pos = p - PyBytes_AS_STRING(packobj);
953 if (pos != nbytes) {
967 if (pos != nbytes) {
954 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
968 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
955 (long)pos, (long)nbytes);
969 (long)pos, (long)nbytes);
956 goto bail;
970 goto bail;
957 }
971 }
958
972
959 return packobj;
973 return packobj;
960 bail:
974 bail:
961 Py_XDECREF(mtime_unset);
975 Py_XDECREF(mtime_unset);
962 Py_XDECREF(packobj);
976 Py_XDECREF(packobj);
963 Py_XDECREF(v);
977 Py_XDECREF(v);
964 return NULL;
978 return NULL;
965 }
979 }
966
980
967 #define BUMPED_FIX 1
981 #define BUMPED_FIX 1
968 #define USING_SHA_256 2
982 #define USING_SHA_256 2
969 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
983 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
970
984
971 static PyObject *readshas(const char *source, unsigned char num,
985 static PyObject *readshas(const char *source, unsigned char num,
972 Py_ssize_t hashwidth)
986 Py_ssize_t hashwidth)
973 {
987 {
974 int i;
988 int i;
975 PyObject *list = PyTuple_New(num);
989 PyObject *list = PyTuple_New(num);
976 if (list == NULL) {
990 if (list == NULL) {
977 return NULL;
991 return NULL;
978 }
992 }
979 for (i = 0; i < num; i++) {
993 for (i = 0; i < num; i++) {
980 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
994 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
981 if (hash == NULL) {
995 if (hash == NULL) {
982 Py_DECREF(list);
996 Py_DECREF(list);
983 return NULL;
997 return NULL;
984 }
998 }
985 PyTuple_SET_ITEM(list, i, hash);
999 PyTuple_SET_ITEM(list, i, hash);
986 source += hashwidth;
1000 source += hashwidth;
987 }
1001 }
988 return list;
1002 return list;
989 }
1003 }
990
1004
991 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
1005 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
992 uint32_t *msize)
1006 uint32_t *msize)
993 {
1007 {
994 const char *data = databegin;
1008 const char *data = databegin;
995 const char *meta;
1009 const char *meta;
996
1010
997 double mtime;
1011 double mtime;
998 int16_t tz;
1012 int16_t tz;
999 uint16_t flags;
1013 uint16_t flags;
1000 unsigned char nsuccs, nparents, nmetadata;
1014 unsigned char nsuccs, nparents, nmetadata;
1001 Py_ssize_t hashwidth = 20;
1015 Py_ssize_t hashwidth = 20;
1002
1016
1003 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
1017 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
1004 PyObject *metadata = NULL, *ret = NULL;
1018 PyObject *metadata = NULL, *ret = NULL;
1005 int i;
1019 int i;
1006
1020
1007 if (data + FM1_HEADER_SIZE > dataend) {
1021 if (data + FM1_HEADER_SIZE > dataend) {
1008 goto overflow;
1022 goto overflow;
1009 }
1023 }
1010
1024
1011 *msize = getbe32(data);
1025 *msize = getbe32(data);
1012 data += 4;
1026 data += 4;
1013 mtime = getbefloat64(data);
1027 mtime = getbefloat64(data);
1014 data += 8;
1028 data += 8;
1015 tz = getbeint16(data);
1029 tz = getbeint16(data);
1016 data += 2;
1030 data += 2;
1017 flags = getbeuint16(data);
1031 flags = getbeuint16(data);
1018 data += 2;
1032 data += 2;
1019
1033
1020 if (flags & USING_SHA_256) {
1034 if (flags & USING_SHA_256) {
1021 hashwidth = 32;
1035 hashwidth = 32;
1022 }
1036 }
1023
1037
1024 nsuccs = (unsigned char)(*data++);
1038 nsuccs = (unsigned char)(*data++);
1025 nparents = (unsigned char)(*data++);
1039 nparents = (unsigned char)(*data++);
1026 nmetadata = (unsigned char)(*data++);
1040 nmetadata = (unsigned char)(*data++);
1027
1041
1028 if (databegin + *msize > dataend) {
1042 if (databegin + *msize > dataend) {
1029 goto overflow;
1043 goto overflow;
1030 }
1044 }
1031 dataend = databegin + *msize; /* narrow down to marker size */
1045 dataend = databegin + *msize; /* narrow down to marker size */
1032
1046
1033 if (data + hashwidth > dataend) {
1047 if (data + hashwidth > dataend) {
1034 goto overflow;
1048 goto overflow;
1035 }
1049 }
1036 prec = PyBytes_FromStringAndSize(data, hashwidth);
1050 prec = PyBytes_FromStringAndSize(data, hashwidth);
1037 data += hashwidth;
1051 data += hashwidth;
1038 if (prec == NULL) {
1052 if (prec == NULL) {
1039 goto bail;
1053 goto bail;
1040 }
1054 }
1041
1055
1042 if (data + nsuccs * hashwidth > dataend) {
1056 if (data + nsuccs * hashwidth > dataend) {
1043 goto overflow;
1057 goto overflow;
1044 }
1058 }
1045 succs = readshas(data, nsuccs, hashwidth);
1059 succs = readshas(data, nsuccs, hashwidth);
1046 if (succs == NULL) {
1060 if (succs == NULL) {
1047 goto bail;
1061 goto bail;
1048 }
1062 }
1049 data += nsuccs * hashwidth;
1063 data += nsuccs * hashwidth;
1050
1064
1051 if (nparents == 1 || nparents == 2) {
1065 if (nparents == 1 || nparents == 2) {
1052 if (data + nparents * hashwidth > dataend) {
1066 if (data + nparents * hashwidth > dataend) {
1053 goto overflow;
1067 goto overflow;
1054 }
1068 }
1055 parents = readshas(data, nparents, hashwidth);
1069 parents = readshas(data, nparents, hashwidth);
1056 if (parents == NULL) {
1070 if (parents == NULL) {
1057 goto bail;
1071 goto bail;
1058 }
1072 }
1059 data += nparents * hashwidth;
1073 data += nparents * hashwidth;
1060 } else {
1074 } else {
1061 parents = Py_None;
1075 parents = Py_None;
1062 Py_INCREF(parents);
1076 Py_INCREF(parents);
1063 }
1077 }
1064
1078
1065 if (data + 2 * nmetadata > dataend) {
1079 if (data + 2 * nmetadata > dataend) {
1066 goto overflow;
1080 goto overflow;
1067 }
1081 }
1068 meta = data + (2 * nmetadata);
1082 meta = data + (2 * nmetadata);
1069 metadata = PyTuple_New(nmetadata);
1083 metadata = PyTuple_New(nmetadata);
1070 if (metadata == NULL) {
1084 if (metadata == NULL) {
1071 goto bail;
1085 goto bail;
1072 }
1086 }
1073 for (i = 0; i < nmetadata; i++) {
1087 for (i = 0; i < nmetadata; i++) {
1074 PyObject *tmp, *left = NULL, *right = NULL;
1088 PyObject *tmp, *left = NULL, *right = NULL;
1075 Py_ssize_t leftsize = (unsigned char)(*data++);
1089 Py_ssize_t leftsize = (unsigned char)(*data++);
1076 Py_ssize_t rightsize = (unsigned char)(*data++);
1090 Py_ssize_t rightsize = (unsigned char)(*data++);
1077 if (meta + leftsize + rightsize > dataend) {
1091 if (meta + leftsize + rightsize > dataend) {
1078 goto overflow;
1092 goto overflow;
1079 }
1093 }
1080 left = PyBytes_FromStringAndSize(meta, leftsize);
1094 left = PyBytes_FromStringAndSize(meta, leftsize);
1081 meta += leftsize;
1095 meta += leftsize;
1082 right = PyBytes_FromStringAndSize(meta, rightsize);
1096 right = PyBytes_FromStringAndSize(meta, rightsize);
1083 meta += rightsize;
1097 meta += rightsize;
1084 tmp = PyTuple_New(2);
1098 tmp = PyTuple_New(2);
1085 if (!left || !right || !tmp) {
1099 if (!left || !right || !tmp) {
1086 Py_XDECREF(left);
1100 Py_XDECREF(left);
1087 Py_XDECREF(right);
1101 Py_XDECREF(right);
1088 Py_XDECREF(tmp);
1102 Py_XDECREF(tmp);
1089 goto bail;
1103 goto bail;
1090 }
1104 }
1091 PyTuple_SET_ITEM(tmp, 0, left);
1105 PyTuple_SET_ITEM(tmp, 0, left);
1092 PyTuple_SET_ITEM(tmp, 1, right);
1106 PyTuple_SET_ITEM(tmp, 1, right);
1093 PyTuple_SET_ITEM(metadata, i, tmp);
1107 PyTuple_SET_ITEM(metadata, i, tmp);
1094 }
1108 }
1095 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1109 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
1096 (int)tz * 60, parents);
1110 (int)tz * 60, parents);
1097 goto bail; /* return successfully */
1111 goto bail; /* return successfully */
1098
1112
1099 overflow:
1113 overflow:
1100 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1114 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
1101 bail:
1115 bail:
1102 Py_XDECREF(prec);
1116 Py_XDECREF(prec);
1103 Py_XDECREF(succs);
1117 Py_XDECREF(succs);
1104 Py_XDECREF(metadata);
1118 Py_XDECREF(metadata);
1105 Py_XDECREF(parents);
1119 Py_XDECREF(parents);
1106 return ret;
1120 return ret;
1107 }
1121 }
1108
1122
1109 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1123 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
1110 {
1124 {
1111 const char *data, *dataend;
1125 const char *data, *dataend;
1112 Py_ssize_t datalen, offset, stop;
1126 Py_ssize_t datalen, offset, stop;
1113 PyObject *markers = NULL;
1127 PyObject *markers = NULL;
1114
1128
1115 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1129 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
1116 &offset, &stop)) {
1130 &offset, &stop)) {
1117 return NULL;
1131 return NULL;
1118 }
1132 }
1119 if (offset < 0) {
1133 if (offset < 0) {
1120 PyErr_SetString(PyExc_ValueError,
1134 PyErr_SetString(PyExc_ValueError,
1121 "invalid negative offset in fm1readmarkers");
1135 "invalid negative offset in fm1readmarkers");
1122 return NULL;
1136 return NULL;
1123 }
1137 }
1124 if (stop > datalen) {
1138 if (stop > datalen) {
1125 PyErr_SetString(
1139 PyErr_SetString(
1126 PyExc_ValueError,
1140 PyExc_ValueError,
1127 "stop longer than data length in fm1readmarkers");
1141 "stop longer than data length in fm1readmarkers");
1128 return NULL;
1142 return NULL;
1129 }
1143 }
1130 dataend = data + datalen;
1144 dataend = data + datalen;
1131 data += offset;
1145 data += offset;
1132 markers = PyList_New(0);
1146 markers = PyList_New(0);
1133 if (!markers) {
1147 if (!markers) {
1134 return NULL;
1148 return NULL;
1135 }
1149 }
1136 while (offset < stop) {
1150 while (offset < stop) {
1137 uint32_t msize;
1151 uint32_t msize;
1138 int error;
1152 int error;
1139 PyObject *record = fm1readmarker(data, dataend, &msize);
1153 PyObject *record = fm1readmarker(data, dataend, &msize);
1140 if (!record) {
1154 if (!record) {
1141 goto bail;
1155 goto bail;
1142 }
1156 }
1143 error = PyList_Append(markers, record);
1157 error = PyList_Append(markers, record);
1144 Py_DECREF(record);
1158 Py_DECREF(record);
1145 if (error) {
1159 if (error) {
1146 goto bail;
1160 goto bail;
1147 }
1161 }
1148 data += msize;
1162 data += msize;
1149 offset += msize;
1163 offset += msize;
1150 }
1164 }
1151 return markers;
1165 return markers;
1152 bail:
1166 bail:
1153 Py_DECREF(markers);
1167 Py_DECREF(markers);
1154 return NULL;
1168 return NULL;
1155 }
1169 }
1156
1170
1157 static char parsers_doc[] = "Efficient content parsing.";
1171 static char parsers_doc[] = "Efficient content parsing.";
1158
1172
1159 PyObject *encodedir(PyObject *self, PyObject *args);
1173 PyObject *encodedir(PyObject *self, PyObject *args);
1160 PyObject *pathencode(PyObject *self, PyObject *args);
1174 PyObject *pathencode(PyObject *self, PyObject *args);
1161 PyObject *lowerencode(PyObject *self, PyObject *args);
1175 PyObject *lowerencode(PyObject *self, PyObject *args);
1162 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1176 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1163
1177
1164 static PyMethodDef methods[] = {
1178 static PyMethodDef methods[] = {
1165 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1179 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1166 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1180 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1167 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1181 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1168 "parse a revlog index\n"},
1182 "parse a revlog index\n"},
1169 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1183 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1170 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1184 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1171 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1185 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1172 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1186 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1173 "construct a dict with an expected size\n"},
1187 "construct a dict with an expected size\n"},
1174 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1188 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1175 "make file foldmap\n"},
1189 "make file foldmap\n"},
1176 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1190 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1177 "escape a UTF-8 byte string to JSON (fast path)\n"},
1191 "escape a UTF-8 byte string to JSON (fast path)\n"},
1178 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1192 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1179 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1193 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1180 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1194 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1181 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1195 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1182 "parse v1 obsolete markers\n"},
1196 "parse v1 obsolete markers\n"},
1183 {NULL, NULL}};
1197 {NULL, NULL}};
1184
1198
1185 void dirs_module_init(PyObject *mod);
1199 void dirs_module_init(PyObject *mod);
1186 void manifest_module_init(PyObject *mod);
1200 void manifest_module_init(PyObject *mod);
1187 void revlog_module_init(PyObject *mod);
1201 void revlog_module_init(PyObject *mod);
1188
1202
1189 static const int version = 20;
1203 static const int version = 20;
1190
1204
1191 static void module_init(PyObject *mod)
1205 static void module_init(PyObject *mod)
1192 {
1206 {
1193 PyModule_AddIntConstant(mod, "version", version);
1207 PyModule_AddIntConstant(mod, "version", version);
1194
1208
1195 /* This module constant has two purposes. First, it lets us unit test
1209 /* This module constant has two purposes. First, it lets us unit test
1196 * the ImportError raised without hard-coding any error text. This
1210 * the ImportError raised without hard-coding any error text. This
1197 * means we can change the text in the future without breaking tests,
1211 * means we can change the text in the future without breaking tests,
1198 * even across changesets without a recompile. Second, its presence
1212 * even across changesets without a recompile. Second, its presence
1199 * can be used to determine whether the version-checking logic is
1213 * can be used to determine whether the version-checking logic is
1200 * present, which also helps in testing across changesets without a
1214 * present, which also helps in testing across changesets without a
1201 * recompile. Note that this means the pure-Python version of parsers
1215 * recompile. Note that this means the pure-Python version of parsers
1202 * should not have this module constant. */
1216 * should not have this module constant. */
1203 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1217 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1204
1218
1205 dirs_module_init(mod);
1219 dirs_module_init(mod);
1206 manifest_module_init(mod);
1220 manifest_module_init(mod);
1207 revlog_module_init(mod);
1221 revlog_module_init(mod);
1208
1222
1209 if (PyType_Ready(&dirstateItemType) < 0) {
1223 if (PyType_Ready(&dirstateItemType) < 0) {
1210 return;
1224 return;
1211 }
1225 }
1212 Py_INCREF(&dirstateItemType);
1226 Py_INCREF(&dirstateItemType);
1213 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1227 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1214 }
1228 }
1215
1229
1216 static int check_python_version(void)
1230 static int check_python_version(void)
1217 {
1231 {
1218 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1232 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1219 long hexversion;
1233 long hexversion;
1220 if (!sys) {
1234 if (!sys) {
1221 return -1;
1235 return -1;
1222 }
1236 }
1223 ver = PyObject_GetAttrString(sys, "hexversion");
1237 ver = PyObject_GetAttrString(sys, "hexversion");
1224 Py_DECREF(sys);
1238 Py_DECREF(sys);
1225 if (!ver) {
1239 if (!ver) {
1226 return -1;
1240 return -1;
1227 }
1241 }
1228 hexversion = PyInt_AsLong(ver);
1242 hexversion = PyInt_AsLong(ver);
1229 Py_DECREF(ver);
1243 Py_DECREF(ver);
1230 /* sys.hexversion is a 32-bit number by default, so the -1 case
1244 /* sys.hexversion is a 32-bit number by default, so the -1 case
1231 * should only occur in unusual circumstances (e.g. if sys.hexversion
1245 * should only occur in unusual circumstances (e.g. if sys.hexversion
1232 * is manually set to an invalid value). */
1246 * is manually set to an invalid value). */
1233 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1247 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1234 PyErr_Format(PyExc_ImportError,
1248 PyErr_Format(PyExc_ImportError,
1235 "%s: The Mercurial extension "
1249 "%s: The Mercurial extension "
1236 "modules were compiled with Python " PY_VERSION
1250 "modules were compiled with Python " PY_VERSION
1237 ", but "
1251 ", but "
1238 "Mercurial is currently using Python with "
1252 "Mercurial is currently using Python with "
1239 "sys.hexversion=%ld: "
1253 "sys.hexversion=%ld: "
1240 "Python %s\n at: %s",
1254 "Python %s\n at: %s",
1241 versionerrortext, hexversion, Py_GetVersion(),
1255 versionerrortext, hexversion, Py_GetVersion(),
1242 Py_GetProgramFullPath());
1256 Py_GetProgramFullPath());
1243 return -1;
1257 return -1;
1244 }
1258 }
1245 return 0;
1259 return 0;
1246 }
1260 }
1247
1261
1248 #ifdef IS_PY3K
1262 #ifdef IS_PY3K
1249 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1263 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1250 parsers_doc, -1, methods};
1264 parsers_doc, -1, methods};
1251
1265
1252 PyMODINIT_FUNC PyInit_parsers(void)
1266 PyMODINIT_FUNC PyInit_parsers(void)
1253 {
1267 {
1254 PyObject *mod;
1268 PyObject *mod;
1255
1269
1256 if (check_python_version() == -1)
1270 if (check_python_version() == -1)
1257 return NULL;
1271 return NULL;
1258 mod = PyModule_Create(&parsers_module);
1272 mod = PyModule_Create(&parsers_module);
1259 module_init(mod);
1273 module_init(mod);
1260 return mod;
1274 return mod;
1261 }
1275 }
1262 #else
1276 #else
1263 PyMODINIT_FUNC initparsers(void)
1277 PyMODINIT_FUNC initparsers(void)
1264 {
1278 {
1265 PyObject *mod;
1279 PyObject *mod;
1266
1280
1267 if (check_python_version() == -1) {
1281 if (check_python_version() == -1) {
1268 return;
1282 return;
1269 }
1283 }
1270 mod = Py_InitModule3("parsers", methods, parsers_doc);
1284 mod = Py_InitModule3("parsers", methods, parsers_doc);
1271 module_init(mod);
1285 module_init(mod);
1272 }
1286 }
1273 #endif
1287 #endif
@@ -1,888 +1,890 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 stat
10 import stat
11 import struct
11 import struct
12 import zlib
12 import zlib
13
13
14 from ..node import (
14 from ..node import (
15 nullrev,
15 nullrev,
16 sha1nodeconstants,
16 sha1nodeconstants,
17 )
17 )
18 from ..thirdparty import attr
18 from ..thirdparty import attr
19 from .. import (
19 from .. import (
20 error,
20 error,
21 pycompat,
21 pycompat,
22 revlogutils,
22 revlogutils,
23 util,
23 util,
24 )
24 )
25
25
26 from ..revlogutils import nodemap as nodemaputil
26 from ..revlogutils import nodemap as nodemaputil
27 from ..revlogutils import constants as revlog_constants
27 from ..revlogutils import constants as revlog_constants
28
28
29 stringio = pycompat.bytesio
29 stringio = pycompat.bytesio
30
30
31
31
32 _pack = struct.pack
32 _pack = struct.pack
33 _unpack = struct.unpack
33 _unpack = struct.unpack
34 _compress = zlib.compress
34 _compress = zlib.compress
35 _decompress = zlib.decompress
35 _decompress = zlib.decompress
36
36
37
37
38 # a special value used internally for `size` if the file come from the other parent
38 # a special value used internally for `size` if the file come from the other parent
39 FROM_P2 = -2
39 FROM_P2 = -2
40
40
41 # a special value used internally for `size` if the file is modified/merged/added
41 # a special value used internally for `size` if the file is modified/merged/added
42 NONNORMAL = -1
42 NONNORMAL = -1
43
43
44 # a special value used internally for `time` if the time is ambigeous
44 # a special value used internally for `time` if the time is ambigeous
45 AMBIGUOUS_TIME = -1
45 AMBIGUOUS_TIME = -1
46
46
47 # Bits of the `flags` byte inside a node in the file format
47 # Bits of the `flags` byte inside a node in the file format
48 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
48 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
49 DIRSTATE_V2_P1_TRACKED = 1 << 1
49 DIRSTATE_V2_P1_TRACKED = 1 << 1
50 DIRSTATE_V2_P2_INFO = 1 << 2
50 DIRSTATE_V2_P2_INFO = 1 << 2
51 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 3
51 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 3
52 DIRSTATE_V2_HAS_FILE_MTIME = 1 << 4
52 DIRSTATE_V2_HAS_FILE_MTIME = 1 << 4
53 _DIRSTATE_V2_HAS_DIRCTORY_MTIME = 1 << 5 # Unused when Rust is not available
53 _DIRSTATE_V2_HAS_DIRCTORY_MTIME = 1 << 5 # Unused when Rust is not available
54 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 6
54 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 6
55 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 7
55 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 7
56 DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 8
56 DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 8
57 DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 9
57 DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 9
58 DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 10
58 DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 10
59
59
60
60
61 @attr.s(slots=True, init=False)
61 @attr.s(slots=True, init=False)
62 class DirstateItem(object):
62 class DirstateItem(object):
63 """represent a dirstate entry
63 """represent a dirstate entry
64
64
65 It hold multiple attributes
65 It hold multiple attributes
66
66
67 # about file tracking
67 # about file tracking
68 - wc_tracked: is the file tracked by the working copy
68 - wc_tracked: is the file tracked by the working copy
69 - p1_tracked: is the file tracked in working copy first parent
69 - p1_tracked: is the file tracked in working copy first parent
70 - p2_info: the file has been involved in some merge operation. Either
70 - p2_info: the file has been involved in some merge operation. Either
71 because it was actually merged, or because the p2 version was
71 because it was actually merged, or because the p2 version was
72 ahead, or because some rename moved it there. In either case
72 ahead, or because some rename moved it there. In either case
73 `hg status` will want it displayed as modified.
73 `hg status` will want it displayed as modified.
74
74
75 # about the file state expected from p1 manifest:
75 # about the file state expected from p1 manifest:
76 - mode: the file mode in p1
76 - mode: the file mode in p1
77 - size: the file size in p1
77 - size: the file size in p1
78
78
79 These value can be set to None, which mean we don't have a meaningful value
79 These value can be set to None, which mean we don't have a meaningful value
80 to compare with. Either because we don't really care about them as there
80 to compare with. Either because we don't really care about them as there
81 `status` is known without having to look at the disk or because we don't
81 `status` is known without having to look at the disk or because we don't
82 know these right now and a full comparison will be needed to find out if
82 know these right now and a full comparison will be needed to find out if
83 the file is clean.
83 the file is clean.
84
84
85 # about the file state on disk last time we saw it:
85 # about the file state on disk last time we saw it:
86 - mtime: the last known clean mtime for the file.
86 - mtime: the last known clean mtime for the file.
87
87
88 This value can be set to None if no cachable state exist. Either because we
88 This value can be set to None if no cachable state exist. Either because we
89 do not care (see previous section) or because we could not cache something
89 do not care (see previous section) or because we could not cache something
90 yet.
90 yet.
91 """
91 """
92
92
93 _wc_tracked = attr.ib()
93 _wc_tracked = attr.ib()
94 _p1_tracked = attr.ib()
94 _p1_tracked = attr.ib()
95 _p2_info = attr.ib()
95 _p2_info = attr.ib()
96 _mode = attr.ib()
96 _mode = attr.ib()
97 _size = attr.ib()
97 _size = attr.ib()
98 _mtime = attr.ib()
98 _mtime = attr.ib()
99 _fallback_exec = attr.ib()
99 _fallback_exec = attr.ib()
100 _fallback_symlink = attr.ib()
100 _fallback_symlink = attr.ib()
101
101
102 def __init__(
102 def __init__(
103 self,
103 self,
104 wc_tracked=False,
104 wc_tracked=False,
105 p1_tracked=False,
105 p1_tracked=False,
106 p2_info=False,
106 p2_info=False,
107 has_meaningful_data=True,
107 has_meaningful_data=True,
108 has_meaningful_mtime=True,
108 has_meaningful_mtime=True,
109 parentfiledata=None,
109 parentfiledata=None,
110 fallback_exec=None,
111 fallback_symlink=None,
110 ):
112 ):
111 self._wc_tracked = wc_tracked
113 self._wc_tracked = wc_tracked
112 self._p1_tracked = p1_tracked
114 self._p1_tracked = p1_tracked
113 self._p2_info = p2_info
115 self._p2_info = p2_info
114
116
115 self._fallback_exec = None
117 self._fallback_exec = fallback_exec
116 self._fallback_symlink = None
118 self._fallback_symlink = fallback_symlink
117
119
118 self._mode = None
120 self._mode = None
119 self._size = None
121 self._size = None
120 self._mtime = None
122 self._mtime = None
121 if parentfiledata is None:
123 if parentfiledata is None:
122 has_meaningful_mtime = False
124 has_meaningful_mtime = False
123 has_meaningful_data = False
125 has_meaningful_data = False
124 if has_meaningful_data:
126 if has_meaningful_data:
125 self._mode = parentfiledata[0]
127 self._mode = parentfiledata[0]
126 self._size = parentfiledata[1]
128 self._size = parentfiledata[1]
127 if has_meaningful_mtime:
129 if has_meaningful_mtime:
128 self._mtime = parentfiledata[2]
130 self._mtime = parentfiledata[2]
129
131
130 @classmethod
132 @classmethod
131 def from_v2_data(cls, flags, size, mtime):
133 def from_v2_data(cls, flags, size, mtime):
132 """Build a new DirstateItem object from V2 data"""
134 """Build a new DirstateItem object from V2 data"""
133 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
135 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
134 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_FILE_MTIME)
136 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_FILE_MTIME)
135 mode = None
137 mode = None
136
138
137 if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED:
139 if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED:
138 # we do not have support for this flag in the code yet,
140 # we do not have support for this flag in the code yet,
139 # force a lookup for this file.
141 # force a lookup for this file.
140 has_mode_size = False
142 has_mode_size = False
141 has_meaningful_mtime = False
143 has_meaningful_mtime = False
142
144
143 if has_mode_size:
145 if has_mode_size:
144 assert stat.S_IXUSR == 0o100
146 assert stat.S_IXUSR == 0o100
145 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
147 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
146 mode = 0o755
148 mode = 0o755
147 else:
149 else:
148 mode = 0o644
150 mode = 0o644
149 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
151 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
150 mode |= stat.S_IFLNK
152 mode |= stat.S_IFLNK
151 else:
153 else:
152 mode |= stat.S_IFREG
154 mode |= stat.S_IFREG
153 return cls(
155 return cls(
154 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
156 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
155 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
157 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
156 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
158 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
157 has_meaningful_data=has_mode_size,
159 has_meaningful_data=has_mode_size,
158 has_meaningful_mtime=has_meaningful_mtime,
160 has_meaningful_mtime=has_meaningful_mtime,
159 parentfiledata=(mode, size, mtime),
161 parentfiledata=(mode, size, mtime),
160 )
162 )
161
163
162 @classmethod
164 @classmethod
163 def from_v1_data(cls, state, mode, size, mtime):
165 def from_v1_data(cls, state, mode, size, mtime):
164 """Build a new DirstateItem object from V1 data
166 """Build a new DirstateItem object from V1 data
165
167
166 Since the dirstate-v1 format is frozen, the signature of this function
168 Since the dirstate-v1 format is frozen, the signature of this function
167 is not expected to change, unlike the __init__ one.
169 is not expected to change, unlike the __init__ one.
168 """
170 """
169 if state == b'm':
171 if state == b'm':
170 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
172 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
171 elif state == b'a':
173 elif state == b'a':
172 return cls(wc_tracked=True)
174 return cls(wc_tracked=True)
173 elif state == b'r':
175 elif state == b'r':
174 if size == NONNORMAL:
176 if size == NONNORMAL:
175 p1_tracked = True
177 p1_tracked = True
176 p2_info = True
178 p2_info = True
177 elif size == FROM_P2:
179 elif size == FROM_P2:
178 p1_tracked = False
180 p1_tracked = False
179 p2_info = True
181 p2_info = True
180 else:
182 else:
181 p1_tracked = True
183 p1_tracked = True
182 p2_info = False
184 p2_info = False
183 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
185 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
184 elif state == b'n':
186 elif state == b'n':
185 if size == FROM_P2:
187 if size == FROM_P2:
186 return cls(wc_tracked=True, p2_info=True)
188 return cls(wc_tracked=True, p2_info=True)
187 elif size == NONNORMAL:
189 elif size == NONNORMAL:
188 return cls(wc_tracked=True, p1_tracked=True)
190 return cls(wc_tracked=True, p1_tracked=True)
189 elif mtime == AMBIGUOUS_TIME:
191 elif mtime == AMBIGUOUS_TIME:
190 return cls(
192 return cls(
191 wc_tracked=True,
193 wc_tracked=True,
192 p1_tracked=True,
194 p1_tracked=True,
193 has_meaningful_mtime=False,
195 has_meaningful_mtime=False,
194 parentfiledata=(mode, size, 42),
196 parentfiledata=(mode, size, 42),
195 )
197 )
196 else:
198 else:
197 return cls(
199 return cls(
198 wc_tracked=True,
200 wc_tracked=True,
199 p1_tracked=True,
201 p1_tracked=True,
200 parentfiledata=(mode, size, mtime),
202 parentfiledata=(mode, size, mtime),
201 )
203 )
202 else:
204 else:
203 raise RuntimeError(b'unknown state: %s' % state)
205 raise RuntimeError(b'unknown state: %s' % state)
204
206
205 def set_possibly_dirty(self):
207 def set_possibly_dirty(self):
206 """Mark a file as "possibly dirty"
208 """Mark a file as "possibly dirty"
207
209
208 This means the next status call will have to actually check its content
210 This means the next status call will have to actually check its content
209 to make sure it is correct.
211 to make sure it is correct.
210 """
212 """
211 self._mtime = None
213 self._mtime = None
212
214
213 def set_clean(self, mode, size, mtime):
215 def set_clean(self, mode, size, mtime):
214 """mark a file as "clean" cancelling potential "possibly dirty call"
216 """mark a file as "clean" cancelling potential "possibly dirty call"
215
217
216 Note: this function is a descendant of `dirstate.normal` and is
218 Note: this function is a descendant of `dirstate.normal` and is
217 currently expected to be call on "normal" entry only. There are not
219 currently expected to be call on "normal" entry only. There are not
218 reason for this to not change in the future as long as the ccode is
220 reason for this to not change in the future as long as the ccode is
219 updated to preserve the proper state of the non-normal files.
221 updated to preserve the proper state of the non-normal files.
220 """
222 """
221 self._wc_tracked = True
223 self._wc_tracked = True
222 self._p1_tracked = True
224 self._p1_tracked = True
223 self._mode = mode
225 self._mode = mode
224 self._size = size
226 self._size = size
225 self._mtime = mtime
227 self._mtime = mtime
226
228
227 def set_tracked(self):
229 def set_tracked(self):
228 """mark a file as tracked in the working copy
230 """mark a file as tracked in the working copy
229
231
230 This will ultimately be called by command like `hg add`.
232 This will ultimately be called by command like `hg add`.
231 """
233 """
232 self._wc_tracked = True
234 self._wc_tracked = True
233 # `set_tracked` is replacing various `normallookup` call. So we mark
235 # `set_tracked` is replacing various `normallookup` call. So we mark
234 # the files as needing lookup
236 # the files as needing lookup
235 #
237 #
236 # Consider dropping this in the future in favor of something less broad.
238 # Consider dropping this in the future in favor of something less broad.
237 self._mtime = None
239 self._mtime = None
238
240
239 def set_untracked(self):
241 def set_untracked(self):
240 """mark a file as untracked in the working copy
242 """mark a file as untracked in the working copy
241
243
242 This will ultimately be called by command like `hg remove`.
244 This will ultimately be called by command like `hg remove`.
243 """
245 """
244 self._wc_tracked = False
246 self._wc_tracked = False
245 self._mode = None
247 self._mode = None
246 self._size = None
248 self._size = None
247 self._mtime = None
249 self._mtime = None
248
250
249 def drop_merge_data(self):
251 def drop_merge_data(self):
250 """remove all "merge-only" from a DirstateItem
252 """remove all "merge-only" from a DirstateItem
251
253
252 This is to be call by the dirstatemap code when the second parent is dropped
254 This is to be call by the dirstatemap code when the second parent is dropped
253 """
255 """
254 if self._p2_info:
256 if self._p2_info:
255 self._p2_info = False
257 self._p2_info = False
256 self._mode = None
258 self._mode = None
257 self._size = None
259 self._size = None
258 self._mtime = None
260 self._mtime = None
259
261
260 @property
262 @property
261 def mode(self):
263 def mode(self):
262 return self.v1_mode()
264 return self.v1_mode()
263
265
264 @property
266 @property
265 def size(self):
267 def size(self):
266 return self.v1_size()
268 return self.v1_size()
267
269
268 @property
270 @property
269 def mtime(self):
271 def mtime(self):
270 return self.v1_mtime()
272 return self.v1_mtime()
271
273
272 @property
274 @property
273 def state(self):
275 def state(self):
274 """
276 """
275 States are:
277 States are:
276 n normal
278 n normal
277 m needs merging
279 m needs merging
278 r marked for removal
280 r marked for removal
279 a marked for addition
281 a marked for addition
280
282
281 XXX This "state" is a bit obscure and mostly a direct expression of the
283 XXX This "state" is a bit obscure and mostly a direct expression of the
282 dirstatev1 format. It would make sense to ultimately deprecate it in
284 dirstatev1 format. It would make sense to ultimately deprecate it in
283 favor of the more "semantic" attributes.
285 favor of the more "semantic" attributes.
284 """
286 """
285 if not self.any_tracked:
287 if not self.any_tracked:
286 return b'?'
288 return b'?'
287 return self.v1_state()
289 return self.v1_state()
288
290
289 @property
291 @property
290 def has_fallback_exec(self):
292 def has_fallback_exec(self):
291 """True if "fallback" information are available for the "exec" bit
293 """True if "fallback" information are available for the "exec" bit
292
294
293 Fallback information can be stored in the dirstate to keep track of
295 Fallback information can be stored in the dirstate to keep track of
294 filesystem attribute tracked by Mercurial when the underlying file
296 filesystem attribute tracked by Mercurial when the underlying file
295 system or operating system does not support that property, (e.g.
297 system or operating system does not support that property, (e.g.
296 Windows).
298 Windows).
297
299
298 Not all version of the dirstate on-disk storage support preserving this
300 Not all version of the dirstate on-disk storage support preserving this
299 information.
301 information.
300 """
302 """
301 return self._fallback_exec is not None
303 return self._fallback_exec is not None
302
304
303 @property
305 @property
304 def fallback_exec(self):
306 def fallback_exec(self):
305 """ "fallback" information for the executable bit
307 """ "fallback" information for the executable bit
306
308
307 True if the file should be considered executable when we cannot get
309 True if the file should be considered executable when we cannot get
308 this information from the files system. False if it should be
310 this information from the files system. False if it should be
309 considered non-executable.
311 considered non-executable.
310
312
311 See has_fallback_exec for details."""
313 See has_fallback_exec for details."""
312 return self._fallback_exec
314 return self._fallback_exec
313
315
314 @fallback_exec.setter
316 @fallback_exec.setter
315 def set_fallback_exec(self, value):
317 def set_fallback_exec(self, value):
316 """control "fallback" executable bit
318 """control "fallback" executable bit
317
319
318 Set to:
320 Set to:
319 - True if the file should be considered executable,
321 - True if the file should be considered executable,
320 - False if the file should be considered non-executable,
322 - False if the file should be considered non-executable,
321 - None if we do not have valid fallback data.
323 - None if we do not have valid fallback data.
322
324
323 See has_fallback_exec for details."""
325 See has_fallback_exec for details."""
324 if value is None:
326 if value is None:
325 self._fallback_exec = None
327 self._fallback_exec = None
326 else:
328 else:
327 self._fallback_exec = bool(value)
329 self._fallback_exec = bool(value)
328
330
329 @property
331 @property
330 def has_fallback_symlink(self):
332 def has_fallback_symlink(self):
331 """True if "fallback" information are available for symlink status
333 """True if "fallback" information are available for symlink status
332
334
333 Fallback information can be stored in the dirstate to keep track of
335 Fallback information can be stored in the dirstate to keep track of
334 filesystem attribute tracked by Mercurial when the underlying file
336 filesystem attribute tracked by Mercurial when the underlying file
335 system or operating system does not support that property, (e.g.
337 system or operating system does not support that property, (e.g.
336 Windows).
338 Windows).
337
339
338 Not all version of the dirstate on-disk storage support preserving this
340 Not all version of the dirstate on-disk storage support preserving this
339 information."""
341 information."""
340 return self._fallback_symlink is not None
342 return self._fallback_symlink is not None
341
343
342 @property
344 @property
343 def fallback_symlink(self):
345 def fallback_symlink(self):
344 """ "fallback" information for symlink status
346 """ "fallback" information for symlink status
345
347
346 True if the file should be considered executable when we cannot get
348 True if the file should be considered executable when we cannot get
347 this information from the files system. False if it should be
349 this information from the files system. False if it should be
348 considered non-executable.
350 considered non-executable.
349
351
350 See has_fallback_exec for details."""
352 See has_fallback_exec for details."""
351 return self._fallback_symlink
353 return self._fallback_symlink
352
354
353 @fallback_symlink.setter
355 @fallback_symlink.setter
354 def set_fallback_symlink(self, value):
356 def set_fallback_symlink(self, value):
355 """control "fallback" symlink status
357 """control "fallback" symlink status
356
358
357 Set to:
359 Set to:
358 - True if the file should be considered a symlink,
360 - True if the file should be considered a symlink,
359 - False if the file should be considered not a symlink,
361 - False if the file should be considered not a symlink,
360 - None if we do not have valid fallback data.
362 - None if we do not have valid fallback data.
361
363
362 See has_fallback_symlink for details."""
364 See has_fallback_symlink for details."""
363 if value is None:
365 if value is None:
364 self._fallback_symlink = None
366 self._fallback_symlink = None
365 else:
367 else:
366 self._fallback_symlink = bool(value)
368 self._fallback_symlink = bool(value)
367
369
368 @property
370 @property
369 def tracked(self):
371 def tracked(self):
370 """True is the file is tracked in the working copy"""
372 """True is the file is tracked in the working copy"""
371 return self._wc_tracked
373 return self._wc_tracked
372
374
373 @property
375 @property
374 def any_tracked(self):
376 def any_tracked(self):
375 """True is the file is tracked anywhere (wc or parents)"""
377 """True is the file is tracked anywhere (wc or parents)"""
376 return self._wc_tracked or self._p1_tracked or self._p2_info
378 return self._wc_tracked or self._p1_tracked or self._p2_info
377
379
378 @property
380 @property
379 def added(self):
381 def added(self):
380 """True if the file has been added"""
382 """True if the file has been added"""
381 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
383 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
382
384
383 @property
385 @property
384 def maybe_clean(self):
386 def maybe_clean(self):
385 """True if the file has a chance to be in the "clean" state"""
387 """True if the file has a chance to be in the "clean" state"""
386 if not self._wc_tracked:
388 if not self._wc_tracked:
387 return False
389 return False
388 elif not self._p1_tracked:
390 elif not self._p1_tracked:
389 return False
391 return False
390 elif self._p2_info:
392 elif self._p2_info:
391 return False
393 return False
392 return True
394 return True
393
395
394 @property
396 @property
395 def p1_tracked(self):
397 def p1_tracked(self):
396 """True if the file is tracked in the first parent manifest"""
398 """True if the file is tracked in the first parent manifest"""
397 return self._p1_tracked
399 return self._p1_tracked
398
400
399 @property
401 @property
400 def p2_info(self):
402 def p2_info(self):
401 """True if the file needed to merge or apply any input from p2
403 """True if the file needed to merge or apply any input from p2
402
404
403 See the class documentation for details.
405 See the class documentation for details.
404 """
406 """
405 return self._wc_tracked and self._p2_info
407 return self._wc_tracked and self._p2_info
406
408
407 @property
409 @property
408 def removed(self):
410 def removed(self):
409 """True if the file has been removed"""
411 """True if the file has been removed"""
410 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
412 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
411
413
412 def v2_data(self):
414 def v2_data(self):
413 """Returns (flags, mode, size, mtime) for v2 serialization"""
415 """Returns (flags, mode, size, mtime) for v2 serialization"""
414 flags = 0
416 flags = 0
415 if self._wc_tracked:
417 if self._wc_tracked:
416 flags |= DIRSTATE_V2_WDIR_TRACKED
418 flags |= DIRSTATE_V2_WDIR_TRACKED
417 if self._p1_tracked:
419 if self._p1_tracked:
418 flags |= DIRSTATE_V2_P1_TRACKED
420 flags |= DIRSTATE_V2_P1_TRACKED
419 if self._p2_info:
421 if self._p2_info:
420 flags |= DIRSTATE_V2_P2_INFO
422 flags |= DIRSTATE_V2_P2_INFO
421 if self._mode is not None and self._size is not None:
423 if self._mode is not None and self._size is not None:
422 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
424 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
423 if self.mode & stat.S_IXUSR:
425 if self.mode & stat.S_IXUSR:
424 flags |= DIRSTATE_V2_MODE_EXEC_PERM
426 flags |= DIRSTATE_V2_MODE_EXEC_PERM
425 if stat.S_ISLNK(self.mode):
427 if stat.S_ISLNK(self.mode):
426 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
428 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
427 if self._mtime is not None:
429 if self._mtime is not None:
428 flags |= DIRSTATE_V2_HAS_FILE_MTIME
430 flags |= DIRSTATE_V2_HAS_FILE_MTIME
429 # Note: we do not need to do anything regarding
431 # Note: we do not need to do anything regarding
430 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
432 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
431 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
433 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
432 return (flags, self._size or 0, self._mtime or 0)
434 return (flags, self._size or 0, self._mtime or 0)
433
435
434 def v1_state(self):
436 def v1_state(self):
435 """return a "state" suitable for v1 serialization"""
437 """return a "state" suitable for v1 serialization"""
436 if not self.any_tracked:
438 if not self.any_tracked:
437 # the object has no state to record, this is -currently-
439 # the object has no state to record, this is -currently-
438 # unsupported
440 # unsupported
439 raise RuntimeError('untracked item')
441 raise RuntimeError('untracked item')
440 elif self.removed:
442 elif self.removed:
441 return b'r'
443 return b'r'
442 elif self._p1_tracked and self._p2_info:
444 elif self._p1_tracked and self._p2_info:
443 return b'm'
445 return b'm'
444 elif self.added:
446 elif self.added:
445 return b'a'
447 return b'a'
446 else:
448 else:
447 return b'n'
449 return b'n'
448
450
449 def v1_mode(self):
451 def v1_mode(self):
450 """return a "mode" suitable for v1 serialization"""
452 """return a "mode" suitable for v1 serialization"""
451 return self._mode if self._mode is not None else 0
453 return self._mode if self._mode is not None else 0
452
454
453 def v1_size(self):
455 def v1_size(self):
454 """return a "size" suitable for v1 serialization"""
456 """return a "size" suitable for v1 serialization"""
455 if not self.any_tracked:
457 if not self.any_tracked:
456 # the object has no state to record, this is -currently-
458 # the object has no state to record, this is -currently-
457 # unsupported
459 # unsupported
458 raise RuntimeError('untracked item')
460 raise RuntimeError('untracked item')
459 elif self.removed and self._p1_tracked and self._p2_info:
461 elif self.removed and self._p1_tracked and self._p2_info:
460 return NONNORMAL
462 return NONNORMAL
461 elif self._p2_info:
463 elif self._p2_info:
462 return FROM_P2
464 return FROM_P2
463 elif self.removed:
465 elif self.removed:
464 return 0
466 return 0
465 elif self.added:
467 elif self.added:
466 return NONNORMAL
468 return NONNORMAL
467 elif self._size is None:
469 elif self._size is None:
468 return NONNORMAL
470 return NONNORMAL
469 else:
471 else:
470 return self._size
472 return self._size
471
473
472 def v1_mtime(self):
474 def v1_mtime(self):
473 """return a "mtime" suitable for v1 serialization"""
475 """return a "mtime" suitable for v1 serialization"""
474 if not self.any_tracked:
476 if not self.any_tracked:
475 # the object has no state to record, this is -currently-
477 # the object has no state to record, this is -currently-
476 # unsupported
478 # unsupported
477 raise RuntimeError('untracked item')
479 raise RuntimeError('untracked item')
478 elif self.removed:
480 elif self.removed:
479 return 0
481 return 0
480 elif self._mtime is None:
482 elif self._mtime is None:
481 return AMBIGUOUS_TIME
483 return AMBIGUOUS_TIME
482 elif self._p2_info:
484 elif self._p2_info:
483 return AMBIGUOUS_TIME
485 return AMBIGUOUS_TIME
484 elif not self._p1_tracked:
486 elif not self._p1_tracked:
485 return AMBIGUOUS_TIME
487 return AMBIGUOUS_TIME
486 else:
488 else:
487 return self._mtime
489 return self._mtime
488
490
489 def need_delay(self, now):
491 def need_delay(self, now):
490 """True if the stored mtime would be ambiguous with the current time"""
492 """True if the stored mtime would be ambiguous with the current time"""
491 return self.v1_state() == b'n' and self.v1_mtime() == now
493 return self.v1_state() == b'n' and self.v1_mtime() == now
492
494
493
495
494 def gettype(q):
496 def gettype(q):
495 return int(q & 0xFFFF)
497 return int(q & 0xFFFF)
496
498
497
499
498 class BaseIndexObject(object):
500 class BaseIndexObject(object):
499 # Can I be passed to an algorithme implemented in Rust ?
501 # Can I be passed to an algorithme implemented in Rust ?
500 rust_ext_compat = 0
502 rust_ext_compat = 0
501 # Format of an index entry according to Python's `struct` language
503 # Format of an index entry according to Python's `struct` language
502 index_format = revlog_constants.INDEX_ENTRY_V1
504 index_format = revlog_constants.INDEX_ENTRY_V1
503 # Size of a C unsigned long long int, platform independent
505 # Size of a C unsigned long long int, platform independent
504 big_int_size = struct.calcsize(b'>Q')
506 big_int_size = struct.calcsize(b'>Q')
505 # Size of a C long int, platform independent
507 # Size of a C long int, platform independent
506 int_size = struct.calcsize(b'>i')
508 int_size = struct.calcsize(b'>i')
507 # An empty index entry, used as a default value to be overridden, or nullrev
509 # An empty index entry, used as a default value to be overridden, or nullrev
508 null_item = (
510 null_item = (
509 0,
511 0,
510 0,
512 0,
511 0,
513 0,
512 -1,
514 -1,
513 -1,
515 -1,
514 -1,
516 -1,
515 -1,
517 -1,
516 sha1nodeconstants.nullid,
518 sha1nodeconstants.nullid,
517 0,
519 0,
518 0,
520 0,
519 revlog_constants.COMP_MODE_INLINE,
521 revlog_constants.COMP_MODE_INLINE,
520 revlog_constants.COMP_MODE_INLINE,
522 revlog_constants.COMP_MODE_INLINE,
521 )
523 )
522
524
523 @util.propertycache
525 @util.propertycache
524 def entry_size(self):
526 def entry_size(self):
525 return self.index_format.size
527 return self.index_format.size
526
528
527 @property
529 @property
528 def nodemap(self):
530 def nodemap(self):
529 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
531 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
530 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
532 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
531 return self._nodemap
533 return self._nodemap
532
534
533 @util.propertycache
535 @util.propertycache
534 def _nodemap(self):
536 def _nodemap(self):
535 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
537 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
536 for r in range(0, len(self)):
538 for r in range(0, len(self)):
537 n = self[r][7]
539 n = self[r][7]
538 nodemap[n] = r
540 nodemap[n] = r
539 return nodemap
541 return nodemap
540
542
541 def has_node(self, node):
543 def has_node(self, node):
542 """return True if the node exist in the index"""
544 """return True if the node exist in the index"""
543 return node in self._nodemap
545 return node in self._nodemap
544
546
545 def rev(self, node):
547 def rev(self, node):
546 """return a revision for a node
548 """return a revision for a node
547
549
548 If the node is unknown, raise a RevlogError"""
550 If the node is unknown, raise a RevlogError"""
549 return self._nodemap[node]
551 return self._nodemap[node]
550
552
551 def get_rev(self, node):
553 def get_rev(self, node):
552 """return a revision for a node
554 """return a revision for a node
553
555
554 If the node is unknown, return None"""
556 If the node is unknown, return None"""
555 return self._nodemap.get(node)
557 return self._nodemap.get(node)
556
558
557 def _stripnodes(self, start):
559 def _stripnodes(self, start):
558 if '_nodemap' in vars(self):
560 if '_nodemap' in vars(self):
559 for r in range(start, len(self)):
561 for r in range(start, len(self)):
560 n = self[r][7]
562 n = self[r][7]
561 del self._nodemap[n]
563 del self._nodemap[n]
562
564
563 def clearcaches(self):
565 def clearcaches(self):
564 self.__dict__.pop('_nodemap', None)
566 self.__dict__.pop('_nodemap', None)
565
567
566 def __len__(self):
568 def __len__(self):
567 return self._lgt + len(self._extra)
569 return self._lgt + len(self._extra)
568
570
569 def append(self, tup):
571 def append(self, tup):
570 if '_nodemap' in vars(self):
572 if '_nodemap' in vars(self):
571 self._nodemap[tup[7]] = len(self)
573 self._nodemap[tup[7]] = len(self)
572 data = self._pack_entry(len(self), tup)
574 data = self._pack_entry(len(self), tup)
573 self._extra.append(data)
575 self._extra.append(data)
574
576
575 def _pack_entry(self, rev, entry):
577 def _pack_entry(self, rev, entry):
576 assert entry[8] == 0
578 assert entry[8] == 0
577 assert entry[9] == 0
579 assert entry[9] == 0
578 return self.index_format.pack(*entry[:8])
580 return self.index_format.pack(*entry[:8])
579
581
580 def _check_index(self, i):
582 def _check_index(self, i):
581 if not isinstance(i, int):
583 if not isinstance(i, int):
582 raise TypeError(b"expecting int indexes")
584 raise TypeError(b"expecting int indexes")
583 if i < 0 or i >= len(self):
585 if i < 0 or i >= len(self):
584 raise IndexError
586 raise IndexError
585
587
586 def __getitem__(self, i):
588 def __getitem__(self, i):
587 if i == -1:
589 if i == -1:
588 return self.null_item
590 return self.null_item
589 self._check_index(i)
591 self._check_index(i)
590 if i >= self._lgt:
592 if i >= self._lgt:
591 data = self._extra[i - self._lgt]
593 data = self._extra[i - self._lgt]
592 else:
594 else:
593 index = self._calculate_index(i)
595 index = self._calculate_index(i)
594 data = self._data[index : index + self.entry_size]
596 data = self._data[index : index + self.entry_size]
595 r = self._unpack_entry(i, data)
597 r = self._unpack_entry(i, data)
596 if self._lgt and i == 0:
598 if self._lgt and i == 0:
597 offset = revlogutils.offset_type(0, gettype(r[0]))
599 offset = revlogutils.offset_type(0, gettype(r[0]))
598 r = (offset,) + r[1:]
600 r = (offset,) + r[1:]
599 return r
601 return r
600
602
601 def _unpack_entry(self, rev, data):
603 def _unpack_entry(self, rev, data):
602 r = self.index_format.unpack(data)
604 r = self.index_format.unpack(data)
603 r = r + (
605 r = r + (
604 0,
606 0,
605 0,
607 0,
606 revlog_constants.COMP_MODE_INLINE,
608 revlog_constants.COMP_MODE_INLINE,
607 revlog_constants.COMP_MODE_INLINE,
609 revlog_constants.COMP_MODE_INLINE,
608 )
610 )
609 return r
611 return r
610
612
611 def pack_header(self, header):
613 def pack_header(self, header):
612 """pack header information as binary"""
614 """pack header information as binary"""
613 v_fmt = revlog_constants.INDEX_HEADER
615 v_fmt = revlog_constants.INDEX_HEADER
614 return v_fmt.pack(header)
616 return v_fmt.pack(header)
615
617
616 def entry_binary(self, rev):
618 def entry_binary(self, rev):
617 """return the raw binary string representing a revision"""
619 """return the raw binary string representing a revision"""
618 entry = self[rev]
620 entry = self[rev]
619 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
621 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
620 if rev == 0:
622 if rev == 0:
621 p = p[revlog_constants.INDEX_HEADER.size :]
623 p = p[revlog_constants.INDEX_HEADER.size :]
622 return p
624 return p
623
625
624
626
625 class IndexObject(BaseIndexObject):
627 class IndexObject(BaseIndexObject):
626 def __init__(self, data):
628 def __init__(self, data):
627 assert len(data) % self.entry_size == 0, (
629 assert len(data) % self.entry_size == 0, (
628 len(data),
630 len(data),
629 self.entry_size,
631 self.entry_size,
630 len(data) % self.entry_size,
632 len(data) % self.entry_size,
631 )
633 )
632 self._data = data
634 self._data = data
633 self._lgt = len(data) // self.entry_size
635 self._lgt = len(data) // self.entry_size
634 self._extra = []
636 self._extra = []
635
637
636 def _calculate_index(self, i):
638 def _calculate_index(self, i):
637 return i * self.entry_size
639 return i * self.entry_size
638
640
639 def __delitem__(self, i):
641 def __delitem__(self, i):
640 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
642 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
641 raise ValueError(b"deleting slices only supports a:-1 with step 1")
643 raise ValueError(b"deleting slices only supports a:-1 with step 1")
642 i = i.start
644 i = i.start
643 self._check_index(i)
645 self._check_index(i)
644 self._stripnodes(i)
646 self._stripnodes(i)
645 if i < self._lgt:
647 if i < self._lgt:
646 self._data = self._data[: i * self.entry_size]
648 self._data = self._data[: i * self.entry_size]
647 self._lgt = i
649 self._lgt = i
648 self._extra = []
650 self._extra = []
649 else:
651 else:
650 self._extra = self._extra[: i - self._lgt]
652 self._extra = self._extra[: i - self._lgt]
651
653
652
654
653 class PersistentNodeMapIndexObject(IndexObject):
655 class PersistentNodeMapIndexObject(IndexObject):
654 """a Debug oriented class to test persistent nodemap
656 """a Debug oriented class to test persistent nodemap
655
657
656 We need a simple python object to test API and higher level behavior. See
658 We need a simple python object to test API and higher level behavior. See
657 the Rust implementation for more serious usage. This should be used only
659 the Rust implementation for more serious usage. This should be used only
658 through the dedicated `devel.persistent-nodemap` config.
660 through the dedicated `devel.persistent-nodemap` config.
659 """
661 """
660
662
661 def nodemap_data_all(self):
663 def nodemap_data_all(self):
662 """Return bytes containing a full serialization of a nodemap
664 """Return bytes containing a full serialization of a nodemap
663
665
664 The nodemap should be valid for the full set of revisions in the
666 The nodemap should be valid for the full set of revisions in the
665 index."""
667 index."""
666 return nodemaputil.persistent_data(self)
668 return nodemaputil.persistent_data(self)
667
669
668 def nodemap_data_incremental(self):
670 def nodemap_data_incremental(self):
669 """Return bytes containing a incremental update to persistent nodemap
671 """Return bytes containing a incremental update to persistent nodemap
670
672
671 This containst the data for an append-only update of the data provided
673 This containst the data for an append-only update of the data provided
672 in the last call to `update_nodemap_data`.
674 in the last call to `update_nodemap_data`.
673 """
675 """
674 if self._nm_root is None:
676 if self._nm_root is None:
675 return None
677 return None
676 docket = self._nm_docket
678 docket = self._nm_docket
677 changed, data = nodemaputil.update_persistent_data(
679 changed, data = nodemaputil.update_persistent_data(
678 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
680 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
679 )
681 )
680
682
681 self._nm_root = self._nm_max_idx = self._nm_docket = None
683 self._nm_root = self._nm_max_idx = self._nm_docket = None
682 return docket, changed, data
684 return docket, changed, data
683
685
684 def update_nodemap_data(self, docket, nm_data):
686 def update_nodemap_data(self, docket, nm_data):
685 """provide full block of persisted binary data for a nodemap
687 """provide full block of persisted binary data for a nodemap
686
688
687 The data are expected to come from disk. See `nodemap_data_all` for a
689 The data are expected to come from disk. See `nodemap_data_all` for a
688 produceur of such data."""
690 produceur of such data."""
689 if nm_data is not None:
691 if nm_data is not None:
690 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
692 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
691 if self._nm_root:
693 if self._nm_root:
692 self._nm_docket = docket
694 self._nm_docket = docket
693 else:
695 else:
694 self._nm_root = self._nm_max_idx = self._nm_docket = None
696 self._nm_root = self._nm_max_idx = self._nm_docket = None
695
697
696
698
697 class InlinedIndexObject(BaseIndexObject):
699 class InlinedIndexObject(BaseIndexObject):
698 def __init__(self, data, inline=0):
700 def __init__(self, data, inline=0):
699 self._data = data
701 self._data = data
700 self._lgt = self._inline_scan(None)
702 self._lgt = self._inline_scan(None)
701 self._inline_scan(self._lgt)
703 self._inline_scan(self._lgt)
702 self._extra = []
704 self._extra = []
703
705
704 def _inline_scan(self, lgt):
706 def _inline_scan(self, lgt):
705 off = 0
707 off = 0
706 if lgt is not None:
708 if lgt is not None:
707 self._offsets = [0] * lgt
709 self._offsets = [0] * lgt
708 count = 0
710 count = 0
709 while off <= len(self._data) - self.entry_size:
711 while off <= len(self._data) - self.entry_size:
710 start = off + self.big_int_size
712 start = off + self.big_int_size
711 (s,) = struct.unpack(
713 (s,) = struct.unpack(
712 b'>i',
714 b'>i',
713 self._data[start : start + self.int_size],
715 self._data[start : start + self.int_size],
714 )
716 )
715 if lgt is not None:
717 if lgt is not None:
716 self._offsets[count] = off
718 self._offsets[count] = off
717 count += 1
719 count += 1
718 off += self.entry_size + s
720 off += self.entry_size + s
719 if off != len(self._data):
721 if off != len(self._data):
720 raise ValueError(b"corrupted data")
722 raise ValueError(b"corrupted data")
721 return count
723 return count
722
724
723 def __delitem__(self, i):
725 def __delitem__(self, i):
724 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
726 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
725 raise ValueError(b"deleting slices only supports a:-1 with step 1")
727 raise ValueError(b"deleting slices only supports a:-1 with step 1")
726 i = i.start
728 i = i.start
727 self._check_index(i)
729 self._check_index(i)
728 self._stripnodes(i)
730 self._stripnodes(i)
729 if i < self._lgt:
731 if i < self._lgt:
730 self._offsets = self._offsets[:i]
732 self._offsets = self._offsets[:i]
731 self._lgt = i
733 self._lgt = i
732 self._extra = []
734 self._extra = []
733 else:
735 else:
734 self._extra = self._extra[: i - self._lgt]
736 self._extra = self._extra[: i - self._lgt]
735
737
736 def _calculate_index(self, i):
738 def _calculate_index(self, i):
737 return self._offsets[i]
739 return self._offsets[i]
738
740
739
741
740 def parse_index2(data, inline, revlogv2=False):
742 def parse_index2(data, inline, revlogv2=False):
741 if not inline:
743 if not inline:
742 cls = IndexObject2 if revlogv2 else IndexObject
744 cls = IndexObject2 if revlogv2 else IndexObject
743 return cls(data), None
745 return cls(data), None
744 cls = InlinedIndexObject
746 cls = InlinedIndexObject
745 return cls(data, inline), (0, data)
747 return cls(data, inline), (0, data)
746
748
747
749
748 def parse_index_cl_v2(data):
750 def parse_index_cl_v2(data):
749 return IndexChangelogV2(data), None
751 return IndexChangelogV2(data), None
750
752
751
753
752 class IndexObject2(IndexObject):
754 class IndexObject2(IndexObject):
753 index_format = revlog_constants.INDEX_ENTRY_V2
755 index_format = revlog_constants.INDEX_ENTRY_V2
754
756
755 def replace_sidedata_info(
757 def replace_sidedata_info(
756 self,
758 self,
757 rev,
759 rev,
758 sidedata_offset,
760 sidedata_offset,
759 sidedata_length,
761 sidedata_length,
760 offset_flags,
762 offset_flags,
761 compression_mode,
763 compression_mode,
762 ):
764 ):
763 """
765 """
764 Replace an existing index entry's sidedata offset and length with new
766 Replace an existing index entry's sidedata offset and length with new
765 ones.
767 ones.
766 This cannot be used outside of the context of sidedata rewriting,
768 This cannot be used outside of the context of sidedata rewriting,
767 inside the transaction that creates the revision `rev`.
769 inside the transaction that creates the revision `rev`.
768 """
770 """
769 if rev < 0:
771 if rev < 0:
770 raise KeyError
772 raise KeyError
771 self._check_index(rev)
773 self._check_index(rev)
772 if rev < self._lgt:
774 if rev < self._lgt:
773 msg = b"cannot rewrite entries outside of this transaction"
775 msg = b"cannot rewrite entries outside of this transaction"
774 raise KeyError(msg)
776 raise KeyError(msg)
775 else:
777 else:
776 entry = list(self[rev])
778 entry = list(self[rev])
777 entry[0] = offset_flags
779 entry[0] = offset_flags
778 entry[8] = sidedata_offset
780 entry[8] = sidedata_offset
779 entry[9] = sidedata_length
781 entry[9] = sidedata_length
780 entry[11] = compression_mode
782 entry[11] = compression_mode
781 entry = tuple(entry)
783 entry = tuple(entry)
782 new = self._pack_entry(rev, entry)
784 new = self._pack_entry(rev, entry)
783 self._extra[rev - self._lgt] = new
785 self._extra[rev - self._lgt] = new
784
786
785 def _unpack_entry(self, rev, data):
787 def _unpack_entry(self, rev, data):
786 data = self.index_format.unpack(data)
788 data = self.index_format.unpack(data)
787 entry = data[:10]
789 entry = data[:10]
788 data_comp = data[10] & 3
790 data_comp = data[10] & 3
789 sidedata_comp = (data[10] & (3 << 2)) >> 2
791 sidedata_comp = (data[10] & (3 << 2)) >> 2
790 return entry + (data_comp, sidedata_comp)
792 return entry + (data_comp, sidedata_comp)
791
793
792 def _pack_entry(self, rev, entry):
794 def _pack_entry(self, rev, entry):
793 data = entry[:10]
795 data = entry[:10]
794 data_comp = entry[10] & 3
796 data_comp = entry[10] & 3
795 sidedata_comp = (entry[11] & 3) << 2
797 sidedata_comp = (entry[11] & 3) << 2
796 data += (data_comp | sidedata_comp,)
798 data += (data_comp | sidedata_comp,)
797
799
798 return self.index_format.pack(*data)
800 return self.index_format.pack(*data)
799
801
800 def entry_binary(self, rev):
802 def entry_binary(self, rev):
801 """return the raw binary string representing a revision"""
803 """return the raw binary string representing a revision"""
802 entry = self[rev]
804 entry = self[rev]
803 return self._pack_entry(rev, entry)
805 return self._pack_entry(rev, entry)
804
806
805 def pack_header(self, header):
807 def pack_header(self, header):
806 """pack header information as binary"""
808 """pack header information as binary"""
807 msg = 'version header should go in the docket, not the index: %d'
809 msg = 'version header should go in the docket, not the index: %d'
808 msg %= header
810 msg %= header
809 raise error.ProgrammingError(msg)
811 raise error.ProgrammingError(msg)
810
812
811
813
812 class IndexChangelogV2(IndexObject2):
814 class IndexChangelogV2(IndexObject2):
813 index_format = revlog_constants.INDEX_ENTRY_CL_V2
815 index_format = revlog_constants.INDEX_ENTRY_CL_V2
814
816
815 def _unpack_entry(self, rev, data, r=True):
817 def _unpack_entry(self, rev, data, r=True):
816 items = self.index_format.unpack(data)
818 items = self.index_format.unpack(data)
817 entry = items[:3] + (rev, rev) + items[3:8]
819 entry = items[:3] + (rev, rev) + items[3:8]
818 data_comp = items[8] & 3
820 data_comp = items[8] & 3
819 sidedata_comp = (items[8] >> 2) & 3
821 sidedata_comp = (items[8] >> 2) & 3
820 return entry + (data_comp, sidedata_comp)
822 return entry + (data_comp, sidedata_comp)
821
823
822 def _pack_entry(self, rev, entry):
824 def _pack_entry(self, rev, entry):
823 assert entry[3] == rev, entry[3]
825 assert entry[3] == rev, entry[3]
824 assert entry[4] == rev, entry[4]
826 assert entry[4] == rev, entry[4]
825 data = entry[:3] + entry[5:10]
827 data = entry[:3] + entry[5:10]
826 data_comp = entry[10] & 3
828 data_comp = entry[10] & 3
827 sidedata_comp = (entry[11] & 3) << 2
829 sidedata_comp = (entry[11] & 3) << 2
828 data += (data_comp | sidedata_comp,)
830 data += (data_comp | sidedata_comp,)
829 return self.index_format.pack(*data)
831 return self.index_format.pack(*data)
830
832
831
833
832 def parse_index_devel_nodemap(data, inline):
834 def parse_index_devel_nodemap(data, inline):
833 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
835 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
834 return PersistentNodeMapIndexObject(data), None
836 return PersistentNodeMapIndexObject(data), None
835
837
836
838
837 def parse_dirstate(dmap, copymap, st):
839 def parse_dirstate(dmap, copymap, st):
838 parents = [st[:20], st[20:40]]
840 parents = [st[:20], st[20:40]]
839 # dereference fields so they will be local in loop
841 # dereference fields so they will be local in loop
840 format = b">cllll"
842 format = b">cllll"
841 e_size = struct.calcsize(format)
843 e_size = struct.calcsize(format)
842 pos1 = 40
844 pos1 = 40
843 l = len(st)
845 l = len(st)
844
846
845 # the inner loop
847 # the inner loop
846 while pos1 < l:
848 while pos1 < l:
847 pos2 = pos1 + e_size
849 pos2 = pos1 + e_size
848 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
850 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
849 pos1 = pos2 + e[4]
851 pos1 = pos2 + e[4]
850 f = st[pos2:pos1]
852 f = st[pos2:pos1]
851 if b'\0' in f:
853 if b'\0' in f:
852 f, c = f.split(b'\0')
854 f, c = f.split(b'\0')
853 copymap[f] = c
855 copymap[f] = c
854 dmap[f] = DirstateItem.from_v1_data(*e[:4])
856 dmap[f] = DirstateItem.from_v1_data(*e[:4])
855 return parents
857 return parents
856
858
857
859
858 def pack_dirstate(dmap, copymap, pl, now):
860 def pack_dirstate(dmap, copymap, pl, now):
859 now = int(now)
861 now = int(now)
860 cs = stringio()
862 cs = stringio()
861 write = cs.write
863 write = cs.write
862 write(b"".join(pl))
864 write(b"".join(pl))
863 for f, e in pycompat.iteritems(dmap):
865 for f, e in pycompat.iteritems(dmap):
864 if e.need_delay(now):
866 if e.need_delay(now):
865 # The file was last modified "simultaneously" with the current
867 # The file was last modified "simultaneously" with the current
866 # write to dirstate (i.e. within the same second for file-
868 # write to dirstate (i.e. within the same second for file-
867 # systems with a granularity of 1 sec). This commonly happens
869 # systems with a granularity of 1 sec). This commonly happens
868 # for at least a couple of files on 'update'.
870 # for at least a couple of files on 'update'.
869 # The user could change the file without changing its size
871 # The user could change the file without changing its size
870 # within the same second. Invalidate the file's mtime in
872 # within the same second. Invalidate the file's mtime in
871 # dirstate, forcing future 'status' calls to compare the
873 # dirstate, forcing future 'status' calls to compare the
872 # contents of the file if the size is the same. This prevents
874 # contents of the file if the size is the same. This prevents
873 # mistakenly treating such files as clean.
875 # mistakenly treating such files as clean.
874 e.set_possibly_dirty()
876 e.set_possibly_dirty()
875
877
876 if f in copymap:
878 if f in copymap:
877 f = b"%s\0%s" % (f, copymap[f])
879 f = b"%s\0%s" % (f, copymap[f])
878 e = _pack(
880 e = _pack(
879 b">cllll",
881 b">cllll",
880 e.v1_state(),
882 e.v1_state(),
881 e.v1_mode(),
883 e.v1_mode(),
882 e.v1_size(),
884 e.v1_size(),
883 e.v1_mtime(),
885 e.v1_mtime(),
884 len(f),
886 len(f),
885 )
887 )
886 write(e)
888 write(e)
887 write(f)
889 write(f)
888 return cs.getvalue()
890 return cs.getvalue()
@@ -1,607 +1,621 b''
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
2 use crate::errors::HgError;
2 use crate::errors::HgError;
3 use bitflags::bitflags;
3 use bitflags::bitflags;
4 use std::convert::{TryFrom, TryInto};
4 use std::convert::{TryFrom, TryInto};
5 use std::fs;
5 use std::fs;
6 use std::io;
6 use std::io;
7 use std::time::{SystemTime, UNIX_EPOCH};
7 use std::time::{SystemTime, UNIX_EPOCH};
8
8
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
9 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
10 pub enum EntryState {
10 pub enum EntryState {
11 Normal,
11 Normal,
12 Added,
12 Added,
13 Removed,
13 Removed,
14 Merged,
14 Merged,
15 }
15 }
16
16
17 /// The C implementation uses all signed types. This will be an issue
17 /// The C implementation uses all signed types. This will be an issue
18 /// either when 4GB+ source files are commonplace or in 2038, whichever
18 /// either when 4GB+ source files are commonplace or in 2038, whichever
19 /// comes first.
19 /// comes first.
20 #[derive(Debug, PartialEq, Copy, Clone)]
20 #[derive(Debug, PartialEq, Copy, Clone)]
21 pub struct DirstateEntry {
21 pub struct DirstateEntry {
22 pub(crate) flags: Flags,
22 pub(crate) flags: Flags,
23 mode_size: Option<(u32, u32)>,
23 mode_size: Option<(u32, u32)>,
24 mtime: Option<u32>,
24 mtime: Option<u32>,
25 }
25 }
26
26
27 bitflags! {
27 bitflags! {
28 pub(crate) struct Flags: u8 {
28 pub(crate) struct Flags: u8 {
29 const WDIR_TRACKED = 1 << 0;
29 const WDIR_TRACKED = 1 << 0;
30 const P1_TRACKED = 1 << 1;
30 const P1_TRACKED = 1 << 1;
31 const P2_INFO = 1 << 2;
31 const P2_INFO = 1 << 2;
32 const HAS_FALLBACK_EXEC = 1 << 3;
32 const HAS_FALLBACK_EXEC = 1 << 3;
33 const FALLBACK_EXEC = 1 << 4;
33 const FALLBACK_EXEC = 1 << 4;
34 const HAS_FALLBACK_SYMLINK = 1 << 5;
34 const HAS_FALLBACK_SYMLINK = 1 << 5;
35 const FALLBACK_SYMLINK = 1 << 6;
35 const FALLBACK_SYMLINK = 1 << 6;
36 }
36 }
37 }
37 }
38
38
39 /// A Unix timestamp with nanoseconds precision
39 /// A Unix timestamp with nanoseconds precision
40 #[derive(Copy, Clone)]
40 #[derive(Copy, Clone)]
41 pub struct TruncatedTimestamp {
41 pub struct TruncatedTimestamp {
42 truncated_seconds: u32,
42 truncated_seconds: u32,
43 /// Always in the `0 .. 1_000_000_000` range.
43 /// Always in the `0 .. 1_000_000_000` range.
44 nanoseconds: u32,
44 nanoseconds: u32,
45 }
45 }
46
46
47 impl TruncatedTimestamp {
47 impl TruncatedTimestamp {
48 /// Constructs from a timestamp potentially outside of the supported range,
48 /// Constructs from a timestamp potentially outside of the supported range,
49 /// and truncate the seconds components to its lower 31 bits.
49 /// and truncate the seconds components to its lower 31 bits.
50 ///
50 ///
51 /// Panics if the nanoseconds components is not in the expected range.
51 /// Panics if the nanoseconds components is not in the expected range.
52 pub fn new_truncate(seconds: i64, nanoseconds: u32) -> Self {
52 pub fn new_truncate(seconds: i64, nanoseconds: u32) -> Self {
53 assert!(nanoseconds < NSEC_PER_SEC);
53 assert!(nanoseconds < NSEC_PER_SEC);
54 Self {
54 Self {
55 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
55 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
56 nanoseconds,
56 nanoseconds,
57 }
57 }
58 }
58 }
59
59
60 /// Construct from components. Returns an error if they are not in the
60 /// Construct from components. Returns an error if they are not in the
61 /// expcted range.
61 /// expcted range.
62 pub fn from_already_truncated(
62 pub fn from_already_truncated(
63 truncated_seconds: u32,
63 truncated_seconds: u32,
64 nanoseconds: u32,
64 nanoseconds: u32,
65 ) -> Result<Self, DirstateV2ParseError> {
65 ) -> Result<Self, DirstateV2ParseError> {
66 if truncated_seconds & !RANGE_MASK_31BIT == 0
66 if truncated_seconds & !RANGE_MASK_31BIT == 0
67 && nanoseconds < NSEC_PER_SEC
67 && nanoseconds < NSEC_PER_SEC
68 {
68 {
69 Ok(Self {
69 Ok(Self {
70 truncated_seconds,
70 truncated_seconds,
71 nanoseconds,
71 nanoseconds,
72 })
72 })
73 } else {
73 } else {
74 Err(DirstateV2ParseError)
74 Err(DirstateV2ParseError)
75 }
75 }
76 }
76 }
77
77
78 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
78 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
79 #[cfg(unix)]
79 #[cfg(unix)]
80 {
80 {
81 use std::os::unix::fs::MetadataExt;
81 use std::os::unix::fs::MetadataExt;
82 let seconds = metadata.mtime();
82 let seconds = metadata.mtime();
83 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
83 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
84 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
84 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
85 Ok(Self::new_truncate(seconds, nanoseconds))
85 Ok(Self::new_truncate(seconds, nanoseconds))
86 }
86 }
87 #[cfg(not(unix))]
87 #[cfg(not(unix))]
88 {
88 {
89 metadata.modified().map(Self::from)
89 metadata.modified().map(Self::from)
90 }
90 }
91 }
91 }
92
92
93 /// The lower 31 bits of the number of seconds since the epoch.
93 /// The lower 31 bits of the number of seconds since the epoch.
94 pub fn truncated_seconds(&self) -> u32 {
94 pub fn truncated_seconds(&self) -> u32 {
95 self.truncated_seconds
95 self.truncated_seconds
96 }
96 }
97
97
98 /// The sub-second component of this timestamp, in nanoseconds.
98 /// The sub-second component of this timestamp, in nanoseconds.
99 /// Always in the `0 .. 1_000_000_000` range.
99 /// Always in the `0 .. 1_000_000_000` range.
100 ///
100 ///
101 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
101 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
102 pub fn nanoseconds(&self) -> u32 {
102 pub fn nanoseconds(&self) -> u32 {
103 self.nanoseconds
103 self.nanoseconds
104 }
104 }
105
105
106 /// Returns whether two timestamps are equal modulo 2**31 seconds.
106 /// Returns whether two timestamps are equal modulo 2**31 seconds.
107 ///
107 ///
108 /// If this returns `true`, the original values converted from `SystemTime`
108 /// If this returns `true`, the original values converted from `SystemTime`
109 /// or given to `new_truncate` were very likely equal. A false positive is
109 /// or given to `new_truncate` were very likely equal. A false positive is
110 /// possible if they were exactly a multiple of 2**31 seconds apart (around
110 /// possible if they were exactly a multiple of 2**31 seconds apart (around
111 /// 68 years). This is deemed very unlikely to happen by chance, especially
111 /// 68 years). This is deemed very unlikely to happen by chance, especially
112 /// on filesystems that support sub-second precision.
112 /// on filesystems that support sub-second precision.
113 ///
113 ///
114 /// If someone is manipulating the modification times of some files to
114 /// If someone is manipulating the modification times of some files to
115 /// intentionally make `hg status` return incorrect results, not truncating
115 /// intentionally make `hg status` return incorrect results, not truncating
116 /// wouldn’t help much since they can set exactly the expected timestamp.
116 /// wouldn’t help much since they can set exactly the expected timestamp.
117 pub fn very_likely_equal(self, other: Self) -> bool {
117 pub fn very_likely_equal(self, other: Self) -> bool {
118 self.truncated_seconds == other.truncated_seconds
118 self.truncated_seconds == other.truncated_seconds
119 && self.nanoseconds == other.nanoseconds
119 && self.nanoseconds == other.nanoseconds
120 }
120 }
121
121
122 pub fn very_likely_equal_to_mtime_of(
122 pub fn very_likely_equal_to_mtime_of(
123 self,
123 self,
124 metadata: &fs::Metadata,
124 metadata: &fs::Metadata,
125 ) -> io::Result<bool> {
125 ) -> io::Result<bool> {
126 Ok(self.very_likely_equal(Self::for_mtime_of(metadata)?))
126 Ok(self.very_likely_equal(Self::for_mtime_of(metadata)?))
127 }
127 }
128 }
128 }
129
129
130 impl From<SystemTime> for TruncatedTimestamp {
130 impl From<SystemTime> for TruncatedTimestamp {
131 fn from(system_time: SystemTime) -> Self {
131 fn from(system_time: SystemTime) -> Self {
132 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
132 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
133 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
133 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
134 // We want to effectively access its fields, but the Rust standard
134 // We want to effectively access its fields, but the Rust standard
135 // library does not expose them. The best we can do is:
135 // library does not expose them. The best we can do is:
136 let seconds;
136 let seconds;
137 let nanoseconds;
137 let nanoseconds;
138 match system_time.duration_since(UNIX_EPOCH) {
138 match system_time.duration_since(UNIX_EPOCH) {
139 Ok(duration) => {
139 Ok(duration) => {
140 seconds = duration.as_secs() as i64;
140 seconds = duration.as_secs() as i64;
141 nanoseconds = duration.subsec_nanos();
141 nanoseconds = duration.subsec_nanos();
142 }
142 }
143 Err(error) => {
143 Err(error) => {
144 // `system_time` is before `UNIX_EPOCH`.
144 // `system_time` is before `UNIX_EPOCH`.
145 // We need to undo this algorithm:
145 // We need to undo this algorithm:
146 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
146 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
147 let negative = error.duration();
147 let negative = error.duration();
148 let negative_secs = negative.as_secs() as i64;
148 let negative_secs = negative.as_secs() as i64;
149 let negative_nanos = negative.subsec_nanos();
149 let negative_nanos = negative.subsec_nanos();
150 if negative_nanos == 0 {
150 if negative_nanos == 0 {
151 seconds = -negative_secs;
151 seconds = -negative_secs;
152 nanoseconds = 0;
152 nanoseconds = 0;
153 } else {
153 } else {
154 // For example if `system_time` was 4.3 seconds before
154 // For example if `system_time` was 4.3 seconds before
155 // the Unix epoch we get a Duration that represents
155 // the Unix epoch we get a Duration that represents
156 // `(-4, -0.3)` but we want `(-5, +0.7)`:
156 // `(-4, -0.3)` but we want `(-5, +0.7)`:
157 seconds = -1 - negative_secs;
157 seconds = -1 - negative_secs;
158 nanoseconds = NSEC_PER_SEC - negative_nanos;
158 nanoseconds = NSEC_PER_SEC - negative_nanos;
159 }
159 }
160 }
160 }
161 };
161 };
162 Self::new_truncate(seconds, nanoseconds)
162 Self::new_truncate(seconds, nanoseconds)
163 }
163 }
164 }
164 }
165
165
166 const NSEC_PER_SEC: u32 = 1_000_000_000;
166 const NSEC_PER_SEC: u32 = 1_000_000_000;
167 const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
167 const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
168
168
169 pub const MTIME_UNSET: i32 = -1;
169 pub const MTIME_UNSET: i32 = -1;
170
170
171 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
171 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
172 /// other parent. This allows revert to pick the right status back during a
172 /// other parent. This allows revert to pick the right status back during a
173 /// merge.
173 /// merge.
174 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
174 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
175 /// A special value used for internal representation of special case in
175 /// A special value used for internal representation of special case in
176 /// dirstate v1 format.
176 /// dirstate v1 format.
177 pub const SIZE_NON_NORMAL: i32 = -1;
177 pub const SIZE_NON_NORMAL: i32 = -1;
178
178
179 impl DirstateEntry {
179 impl DirstateEntry {
180 pub fn from_v2_data(
180 pub fn from_v2_data(
181 wdir_tracked: bool,
181 wdir_tracked: bool,
182 p1_tracked: bool,
182 p1_tracked: bool,
183 p2_info: bool,
183 p2_info: bool,
184 mode_size: Option<(u32, u32)>,
184 mode_size: Option<(u32, u32)>,
185 mtime: Option<u32>,
185 mtime: Option<u32>,
186 fallback_exec: Option<bool>,
187 fallback_symlink: Option<bool>,
186 ) -> Self {
188 ) -> Self {
187 if let Some((mode, size)) = mode_size {
189 if let Some((mode, size)) = mode_size {
188 // TODO: return an error for out of range values?
190 // TODO: return an error for out of range values?
189 assert!(mode & !RANGE_MASK_31BIT == 0);
191 assert!(mode & !RANGE_MASK_31BIT == 0);
190 assert!(size & !RANGE_MASK_31BIT == 0);
192 assert!(size & !RANGE_MASK_31BIT == 0);
191 }
193 }
192 if let Some(mtime) = mtime {
194 if let Some(mtime) = mtime {
193 assert!(mtime & !RANGE_MASK_31BIT == 0);
195 assert!(mtime & !RANGE_MASK_31BIT == 0);
194 }
196 }
195 let mut flags = Flags::empty();
197 let mut flags = Flags::empty();
196 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
198 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
197 flags.set(Flags::P1_TRACKED, p1_tracked);
199 flags.set(Flags::P1_TRACKED, p1_tracked);
198 flags.set(Flags::P2_INFO, p2_info);
200 flags.set(Flags::P2_INFO, p2_info);
201 if let Some(exec) = fallback_exec {
202 flags.insert(Flags::HAS_FALLBACK_EXEC);
203 if exec {
204 flags.insert(Flags::FALLBACK_EXEC);
205 }
206 }
207 if let Some(exec) = fallback_symlink {
208 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
209 if exec {
210 flags.insert(Flags::FALLBACK_SYMLINK);
211 }
212 }
199 Self {
213 Self {
200 flags,
214 flags,
201 mode_size,
215 mode_size,
202 mtime,
216 mtime,
203 }
217 }
204 }
218 }
205
219
206 pub fn from_v1_data(
220 pub fn from_v1_data(
207 state: EntryState,
221 state: EntryState,
208 mode: i32,
222 mode: i32,
209 size: i32,
223 size: i32,
210 mtime: i32,
224 mtime: i32,
211 ) -> Self {
225 ) -> Self {
212 match state {
226 match state {
213 EntryState::Normal => {
227 EntryState::Normal => {
214 if size == SIZE_FROM_OTHER_PARENT {
228 if size == SIZE_FROM_OTHER_PARENT {
215 Self {
229 Self {
216 // might be missing P1_TRACKED
230 // might be missing P1_TRACKED
217 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
231 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
218 mode_size: None,
232 mode_size: None,
219 mtime: None,
233 mtime: None,
220 }
234 }
221 } else if size == SIZE_NON_NORMAL {
235 } else if size == SIZE_NON_NORMAL {
222 Self {
236 Self {
223 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
237 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
224 mode_size: None,
238 mode_size: None,
225 mtime: None,
239 mtime: None,
226 }
240 }
227 } else if mtime == MTIME_UNSET {
241 } else if mtime == MTIME_UNSET {
228 // TODO: return an error for negative values?
242 // TODO: return an error for negative values?
229 let mode = u32::try_from(mode).unwrap();
243 let mode = u32::try_from(mode).unwrap();
230 let size = u32::try_from(size).unwrap();
244 let size = u32::try_from(size).unwrap();
231 Self {
245 Self {
232 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
246 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
233 mode_size: Some((mode, size)),
247 mode_size: Some((mode, size)),
234 mtime: None,
248 mtime: None,
235 }
249 }
236 } else {
250 } else {
237 // TODO: return an error for negative values?
251 // TODO: return an error for negative values?
238 let mode = u32::try_from(mode).unwrap();
252 let mode = u32::try_from(mode).unwrap();
239 let size = u32::try_from(size).unwrap();
253 let size = u32::try_from(size).unwrap();
240 let mtime = u32::try_from(mtime).unwrap();
254 let mtime = u32::try_from(mtime).unwrap();
241 Self {
255 Self {
242 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
256 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
243 mode_size: Some((mode, size)),
257 mode_size: Some((mode, size)),
244 mtime: Some(mtime),
258 mtime: Some(mtime),
245 }
259 }
246 }
260 }
247 }
261 }
248 EntryState::Added => Self {
262 EntryState::Added => Self {
249 flags: Flags::WDIR_TRACKED,
263 flags: Flags::WDIR_TRACKED,
250 mode_size: None,
264 mode_size: None,
251 mtime: None,
265 mtime: None,
252 },
266 },
253 EntryState::Removed => Self {
267 EntryState::Removed => Self {
254 flags: if size == SIZE_NON_NORMAL {
268 flags: if size == SIZE_NON_NORMAL {
255 Flags::P1_TRACKED | Flags::P2_INFO
269 Flags::P1_TRACKED | Flags::P2_INFO
256 } else if size == SIZE_FROM_OTHER_PARENT {
270 } else if size == SIZE_FROM_OTHER_PARENT {
257 // We don’t know if P1_TRACKED should be set (file history)
271 // We don’t know if P1_TRACKED should be set (file history)
258 Flags::P2_INFO
272 Flags::P2_INFO
259 } else {
273 } else {
260 Flags::P1_TRACKED
274 Flags::P1_TRACKED
261 },
275 },
262 mode_size: None,
276 mode_size: None,
263 mtime: None,
277 mtime: None,
264 },
278 },
265 EntryState::Merged => Self {
279 EntryState::Merged => Self {
266 flags: Flags::WDIR_TRACKED
280 flags: Flags::WDIR_TRACKED
267 | Flags::P1_TRACKED // might not be true because of rename ?
281 | Flags::P1_TRACKED // might not be true because of rename ?
268 | Flags::P2_INFO, // might not be true because of rename ?
282 | Flags::P2_INFO, // might not be true because of rename ?
269 mode_size: None,
283 mode_size: None,
270 mtime: None,
284 mtime: None,
271 },
285 },
272 }
286 }
273 }
287 }
274
288
275 /// Creates a new entry in "removed" state.
289 /// Creates a new entry in "removed" state.
276 ///
290 ///
277 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
291 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
278 /// `SIZE_FROM_OTHER_PARENT`
292 /// `SIZE_FROM_OTHER_PARENT`
279 pub fn new_removed(size: i32) -> Self {
293 pub fn new_removed(size: i32) -> Self {
280 Self::from_v1_data(EntryState::Removed, 0, size, 0)
294 Self::from_v1_data(EntryState::Removed, 0, size, 0)
281 }
295 }
282
296
283 pub fn tracked(&self) -> bool {
297 pub fn tracked(&self) -> bool {
284 self.flags.contains(Flags::WDIR_TRACKED)
298 self.flags.contains(Flags::WDIR_TRACKED)
285 }
299 }
286
300
287 pub fn p1_tracked(&self) -> bool {
301 pub fn p1_tracked(&self) -> bool {
288 self.flags.contains(Flags::P1_TRACKED)
302 self.flags.contains(Flags::P1_TRACKED)
289 }
303 }
290
304
291 fn in_either_parent(&self) -> bool {
305 fn in_either_parent(&self) -> bool {
292 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
306 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
293 }
307 }
294
308
295 pub fn removed(&self) -> bool {
309 pub fn removed(&self) -> bool {
296 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
310 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
297 }
311 }
298
312
299 pub fn p2_info(&self) -> bool {
313 pub fn p2_info(&self) -> bool {
300 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
314 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
301 }
315 }
302
316
303 pub fn added(&self) -> bool {
317 pub fn added(&self) -> bool {
304 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
318 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
305 }
319 }
306
320
307 pub fn maybe_clean(&self) -> bool {
321 pub fn maybe_clean(&self) -> bool {
308 if !self.flags.contains(Flags::WDIR_TRACKED) {
322 if !self.flags.contains(Flags::WDIR_TRACKED) {
309 false
323 false
310 } else if !self.flags.contains(Flags::P1_TRACKED) {
324 } else if !self.flags.contains(Flags::P1_TRACKED) {
311 false
325 false
312 } else if self.flags.contains(Flags::P2_INFO) {
326 } else if self.flags.contains(Flags::P2_INFO) {
313 false
327 false
314 } else {
328 } else {
315 true
329 true
316 }
330 }
317 }
331 }
318
332
319 pub fn any_tracked(&self) -> bool {
333 pub fn any_tracked(&self) -> bool {
320 self.flags.intersects(
334 self.flags.intersects(
321 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
335 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
322 )
336 )
323 }
337 }
324
338
325 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
339 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
326 pub(crate) fn v2_data(
340 pub(crate) fn v2_data(
327 &self,
341 &self,
328 ) -> (bool, bool, bool, Option<(u32, u32)>, Option<u32>) {
342 ) -> (bool, bool, bool, Option<(u32, u32)>, Option<u32>) {
329 if !self.any_tracked() {
343 if !self.any_tracked() {
330 // TODO: return an Option instead?
344 // TODO: return an Option instead?
331 panic!("Accessing v1_state of an untracked DirstateEntry")
345 panic!("Accessing v1_state of an untracked DirstateEntry")
332 }
346 }
333 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
347 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
334 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
348 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
335 let p2_info = self.flags.contains(Flags::P2_INFO);
349 let p2_info = self.flags.contains(Flags::P2_INFO);
336 let mode_size = self.mode_size;
350 let mode_size = self.mode_size;
337 let mtime = self.mtime;
351 let mtime = self.mtime;
338 (wdir_tracked, p1_tracked, p2_info, mode_size, mtime)
352 (wdir_tracked, p1_tracked, p2_info, mode_size, mtime)
339 }
353 }
340
354
341 fn v1_state(&self) -> EntryState {
355 fn v1_state(&self) -> EntryState {
342 if !self.any_tracked() {
356 if !self.any_tracked() {
343 // TODO: return an Option instead?
357 // TODO: return an Option instead?
344 panic!("Accessing v1_state of an untracked DirstateEntry")
358 panic!("Accessing v1_state of an untracked DirstateEntry")
345 }
359 }
346 if self.removed() {
360 if self.removed() {
347 EntryState::Removed
361 EntryState::Removed
348 } else if self
362 } else if self
349 .flags
363 .flags
350 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
364 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
351 {
365 {
352 EntryState::Merged
366 EntryState::Merged
353 } else if self.added() {
367 } else if self.added() {
354 EntryState::Added
368 EntryState::Added
355 } else {
369 } else {
356 EntryState::Normal
370 EntryState::Normal
357 }
371 }
358 }
372 }
359
373
360 fn v1_mode(&self) -> i32 {
374 fn v1_mode(&self) -> i32 {
361 if let Some((mode, _size)) = self.mode_size {
375 if let Some((mode, _size)) = self.mode_size {
362 i32::try_from(mode).unwrap()
376 i32::try_from(mode).unwrap()
363 } else {
377 } else {
364 0
378 0
365 }
379 }
366 }
380 }
367
381
368 fn v1_size(&self) -> i32 {
382 fn v1_size(&self) -> i32 {
369 if !self.any_tracked() {
383 if !self.any_tracked() {
370 // TODO: return an Option instead?
384 // TODO: return an Option instead?
371 panic!("Accessing v1_size of an untracked DirstateEntry")
385 panic!("Accessing v1_size of an untracked DirstateEntry")
372 }
386 }
373 if self.removed()
387 if self.removed()
374 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
388 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
375 {
389 {
376 SIZE_NON_NORMAL
390 SIZE_NON_NORMAL
377 } else if self.flags.contains(Flags::P2_INFO) {
391 } else if self.flags.contains(Flags::P2_INFO) {
378 SIZE_FROM_OTHER_PARENT
392 SIZE_FROM_OTHER_PARENT
379 } else if self.removed() {
393 } else if self.removed() {
380 0
394 0
381 } else if self.added() {
395 } else if self.added() {
382 SIZE_NON_NORMAL
396 SIZE_NON_NORMAL
383 } else if let Some((_mode, size)) = self.mode_size {
397 } else if let Some((_mode, size)) = self.mode_size {
384 i32::try_from(size).unwrap()
398 i32::try_from(size).unwrap()
385 } else {
399 } else {
386 SIZE_NON_NORMAL
400 SIZE_NON_NORMAL
387 }
401 }
388 }
402 }
389
403
390 fn v1_mtime(&self) -> i32 {
404 fn v1_mtime(&self) -> i32 {
391 if !self.any_tracked() {
405 if !self.any_tracked() {
392 // TODO: return an Option instead?
406 // TODO: return an Option instead?
393 panic!("Accessing v1_mtime of an untracked DirstateEntry")
407 panic!("Accessing v1_mtime of an untracked DirstateEntry")
394 }
408 }
395 if self.removed() {
409 if self.removed() {
396 0
410 0
397 } else if self.flags.contains(Flags::P2_INFO) {
411 } else if self.flags.contains(Flags::P2_INFO) {
398 MTIME_UNSET
412 MTIME_UNSET
399 } else if !self.flags.contains(Flags::P1_TRACKED) {
413 } else if !self.flags.contains(Flags::P1_TRACKED) {
400 MTIME_UNSET
414 MTIME_UNSET
401 } else if let Some(mtime) = self.mtime {
415 } else if let Some(mtime) = self.mtime {
402 i32::try_from(mtime).unwrap()
416 i32::try_from(mtime).unwrap()
403 } else {
417 } else {
404 MTIME_UNSET
418 MTIME_UNSET
405 }
419 }
406 }
420 }
407
421
408 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
422 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
409 pub fn state(&self) -> EntryState {
423 pub fn state(&self) -> EntryState {
410 self.v1_state()
424 self.v1_state()
411 }
425 }
412
426
413 // TODO: return Option?
427 // TODO: return Option?
414 pub fn mode(&self) -> i32 {
428 pub fn mode(&self) -> i32 {
415 self.v1_mode()
429 self.v1_mode()
416 }
430 }
417
431
418 // TODO: return Option?
432 // TODO: return Option?
419 pub fn size(&self) -> i32 {
433 pub fn size(&self) -> i32 {
420 self.v1_size()
434 self.v1_size()
421 }
435 }
422
436
423 // TODO: return Option?
437 // TODO: return Option?
424 pub fn mtime(&self) -> i32 {
438 pub fn mtime(&self) -> i32 {
425 self.v1_mtime()
439 self.v1_mtime()
426 }
440 }
427
441
428 pub fn get_fallback_exec(&self) -> Option<bool> {
442 pub fn get_fallback_exec(&self) -> Option<bool> {
429 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
443 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
430 Some(self.flags.contains(Flags::FALLBACK_EXEC))
444 Some(self.flags.contains(Flags::FALLBACK_EXEC))
431 } else {
445 } else {
432 None
446 None
433 }
447 }
434 }
448 }
435
449
436 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
450 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
437 match value {
451 match value {
438 None => {
452 None => {
439 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
453 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
440 self.flags.remove(Flags::FALLBACK_EXEC);
454 self.flags.remove(Flags::FALLBACK_EXEC);
441 }
455 }
442 Some(exec) => {
456 Some(exec) => {
443 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
457 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
444 if exec {
458 if exec {
445 self.flags.insert(Flags::FALLBACK_EXEC);
459 self.flags.insert(Flags::FALLBACK_EXEC);
446 }
460 }
447 }
461 }
448 }
462 }
449 }
463 }
450
464
451 pub fn get_fallback_symlink(&self) -> Option<bool> {
465 pub fn get_fallback_symlink(&self) -> Option<bool> {
452 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
466 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
453 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
467 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
454 } else {
468 } else {
455 None
469 None
456 }
470 }
457 }
471 }
458
472
459 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
473 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
460 match value {
474 match value {
461 None => {
475 None => {
462 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
476 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
463 self.flags.remove(Flags::FALLBACK_SYMLINK);
477 self.flags.remove(Flags::FALLBACK_SYMLINK);
464 }
478 }
465 Some(symlink) => {
479 Some(symlink) => {
466 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
480 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
467 if symlink {
481 if symlink {
468 self.flags.insert(Flags::FALLBACK_SYMLINK);
482 self.flags.insert(Flags::FALLBACK_SYMLINK);
469 }
483 }
470 }
484 }
471 }
485 }
472 }
486 }
473
487
474 pub fn drop_merge_data(&mut self) {
488 pub fn drop_merge_data(&mut self) {
475 if self.flags.contains(Flags::P2_INFO) {
489 if self.flags.contains(Flags::P2_INFO) {
476 self.flags.remove(Flags::P2_INFO);
490 self.flags.remove(Flags::P2_INFO);
477 self.mode_size = None;
491 self.mode_size = None;
478 self.mtime = None;
492 self.mtime = None;
479 }
493 }
480 }
494 }
481
495
482 pub fn set_possibly_dirty(&mut self) {
496 pub fn set_possibly_dirty(&mut self) {
483 self.mtime = None
497 self.mtime = None
484 }
498 }
485
499
486 pub fn set_clean(&mut self, mode: u32, size: u32, mtime: u32) {
500 pub fn set_clean(&mut self, mode: u32, size: u32, mtime: u32) {
487 let size = size & RANGE_MASK_31BIT;
501 let size = size & RANGE_MASK_31BIT;
488 let mtime = mtime & RANGE_MASK_31BIT;
502 let mtime = mtime & RANGE_MASK_31BIT;
489 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
503 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
490 self.mode_size = Some((mode, size));
504 self.mode_size = Some((mode, size));
491 self.mtime = Some(mtime);
505 self.mtime = Some(mtime);
492 }
506 }
493
507
494 pub fn set_tracked(&mut self) {
508 pub fn set_tracked(&mut self) {
495 self.flags.insert(Flags::WDIR_TRACKED);
509 self.flags.insert(Flags::WDIR_TRACKED);
496 // `set_tracked` is replacing various `normallookup` call. So we mark
510 // `set_tracked` is replacing various `normallookup` call. So we mark
497 // the files as needing lookup
511 // the files as needing lookup
498 //
512 //
499 // Consider dropping this in the future in favor of something less
513 // Consider dropping this in the future in favor of something less
500 // broad.
514 // broad.
501 self.mtime = None;
515 self.mtime = None;
502 }
516 }
503
517
504 pub fn set_untracked(&mut self) {
518 pub fn set_untracked(&mut self) {
505 self.flags.remove(Flags::WDIR_TRACKED);
519 self.flags.remove(Flags::WDIR_TRACKED);
506 self.mode_size = None;
520 self.mode_size = None;
507 self.mtime = None;
521 self.mtime = None;
508 }
522 }
509
523
510 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
524 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
511 /// in the dirstate-v1 format.
525 /// in the dirstate-v1 format.
512 ///
526 ///
513 /// This includes marker values such as `mtime == -1`. In the future we may
527 /// This includes marker values such as `mtime == -1`. In the future we may
514 /// want to not represent these cases that way in memory, but serialization
528 /// want to not represent these cases that way in memory, but serialization
515 /// will need to keep the same format.
529 /// will need to keep the same format.
516 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
530 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
517 (
531 (
518 self.v1_state().into(),
532 self.v1_state().into(),
519 self.v1_mode(),
533 self.v1_mode(),
520 self.v1_size(),
534 self.v1_size(),
521 self.v1_mtime(),
535 self.v1_mtime(),
522 )
536 )
523 }
537 }
524
538
525 pub(crate) fn is_from_other_parent(&self) -> bool {
539 pub(crate) fn is_from_other_parent(&self) -> bool {
526 self.state() == EntryState::Normal
540 self.state() == EntryState::Normal
527 && self.size() == SIZE_FROM_OTHER_PARENT
541 && self.size() == SIZE_FROM_OTHER_PARENT
528 }
542 }
529
543
530 // TODO: other platforms
544 // TODO: other platforms
531 #[cfg(unix)]
545 #[cfg(unix)]
532 pub fn mode_changed(
546 pub fn mode_changed(
533 &self,
547 &self,
534 filesystem_metadata: &std::fs::Metadata,
548 filesystem_metadata: &std::fs::Metadata,
535 ) -> bool {
549 ) -> bool {
536 use std::os::unix::fs::MetadataExt;
550 use std::os::unix::fs::MetadataExt;
537 const EXEC_BIT_MASK: u32 = 0o100;
551 const EXEC_BIT_MASK: u32 = 0o100;
538 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
552 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
539 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
553 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
540 dirstate_exec_bit != fs_exec_bit
554 dirstate_exec_bit != fs_exec_bit
541 }
555 }
542
556
543 /// Returns a `(state, mode, size, mtime)` tuple as for
557 /// Returns a `(state, mode, size, mtime)` tuple as for
544 /// `DirstateMapMethods::debug_iter`.
558 /// `DirstateMapMethods::debug_iter`.
545 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
559 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
546 (self.state().into(), self.mode(), self.size(), self.mtime())
560 (self.state().into(), self.mode(), self.size(), self.mtime())
547 }
561 }
548
562
549 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
563 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
550 self.state() == EntryState::Normal && self.mtime() == now
564 self.state() == EntryState::Normal && self.mtime() == now
551 }
565 }
552
566
553 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
567 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
554 let ambiguous = self.mtime_is_ambiguous(now);
568 let ambiguous = self.mtime_is_ambiguous(now);
555 if ambiguous {
569 if ambiguous {
556 // The file was last modified "simultaneously" with the current
570 // The file was last modified "simultaneously" with the current
557 // write to dirstate (i.e. within the same second for file-
571 // write to dirstate (i.e. within the same second for file-
558 // systems with a granularity of 1 sec). This commonly happens
572 // systems with a granularity of 1 sec). This commonly happens
559 // for at least a couple of files on 'update'.
573 // for at least a couple of files on 'update'.
560 // The user could change the file without changing its size
574 // The user could change the file without changing its size
561 // within the same second. Invalidate the file's mtime in
575 // within the same second. Invalidate the file's mtime in
562 // dirstate, forcing future 'status' calls to compare the
576 // dirstate, forcing future 'status' calls to compare the
563 // contents of the file if the size is the same. This prevents
577 // contents of the file if the size is the same. This prevents
564 // mistakenly treating such files as clean.
578 // mistakenly treating such files as clean.
565 self.set_possibly_dirty()
579 self.set_possibly_dirty()
566 }
580 }
567 ambiguous
581 ambiguous
568 }
582 }
569 }
583 }
570
584
571 impl EntryState {
585 impl EntryState {
572 pub fn is_tracked(self) -> bool {
586 pub fn is_tracked(self) -> bool {
573 use EntryState::*;
587 use EntryState::*;
574 match self {
588 match self {
575 Normal | Added | Merged => true,
589 Normal | Added | Merged => true,
576 Removed => false,
590 Removed => false,
577 }
591 }
578 }
592 }
579 }
593 }
580
594
581 impl TryFrom<u8> for EntryState {
595 impl TryFrom<u8> for EntryState {
582 type Error = HgError;
596 type Error = HgError;
583
597
584 fn try_from(value: u8) -> Result<Self, Self::Error> {
598 fn try_from(value: u8) -> Result<Self, Self::Error> {
585 match value {
599 match value {
586 b'n' => Ok(EntryState::Normal),
600 b'n' => Ok(EntryState::Normal),
587 b'a' => Ok(EntryState::Added),
601 b'a' => Ok(EntryState::Added),
588 b'r' => Ok(EntryState::Removed),
602 b'r' => Ok(EntryState::Removed),
589 b'm' => Ok(EntryState::Merged),
603 b'm' => Ok(EntryState::Merged),
590 _ => Err(HgError::CorruptedRepository(format!(
604 _ => Err(HgError::CorruptedRepository(format!(
591 "Incorrect dirstate entry state {}",
605 "Incorrect dirstate entry state {}",
592 value
606 value
593 ))),
607 ))),
594 }
608 }
595 }
609 }
596 }
610 }
597
611
598 impl Into<u8> for EntryState {
612 impl Into<u8> for EntryState {
599 fn into(self) -> u8 {
613 fn into(self) -> u8 {
600 match self {
614 match self {
601 EntryState::Normal => b'n',
615 EntryState::Normal => b'n',
602 EntryState::Added => b'a',
616 EntryState::Added => b'a',
603 EntryState::Removed => b'r',
617 EntryState::Removed => b'r',
604 EntryState::Merged => b'm',
618 EntryState::Merged => b'm',
605 }
619 }
606 }
620 }
607 }
621 }
@@ -1,748 +1,750 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::TruncatedTimestamp;
5 use crate::dirstate::TruncatedTimestamp;
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
7 use crate::dirstate_tree::path_with_basename::WithBasename;
7 use crate::dirstate_tree::path_with_basename::WithBasename;
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::DirstateEntry;
10 use crate::DirstateEntry;
11 use crate::DirstateError;
11 use crate::DirstateError;
12 use crate::DirstateParents;
12 use crate::DirstateParents;
13 use bitflags::bitflags;
13 use bitflags::bitflags;
14 use bytes_cast::unaligned::{U16Be, U32Be};
14 use bytes_cast::unaligned::{U16Be, U32Be};
15 use bytes_cast::BytesCast;
15 use bytes_cast::BytesCast;
16 use format_bytes::format_bytes;
16 use format_bytes::format_bytes;
17 use std::borrow::Cow;
17 use std::borrow::Cow;
18 use std::convert::{TryFrom, TryInto};
18 use std::convert::{TryFrom, TryInto};
19
19
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
21 /// This a redundant sanity check more than an actual "magic number" since
21 /// This a redundant sanity check more than an actual "magic number" since
22 /// `.hg/requires` already governs which format should be used.
22 /// `.hg/requires` already governs which format should be used.
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
24
24
25 /// Keep space for 256-bit hashes
25 /// Keep space for 256-bit hashes
26 const STORED_NODE_ID_BYTES: usize = 32;
26 const STORED_NODE_ID_BYTES: usize = 32;
27
27
28 /// … even though only 160 bits are used for now, with SHA-1
28 /// … even though only 160 bits are used for now, with SHA-1
29 const USED_NODE_ID_BYTES: usize = 20;
29 const USED_NODE_ID_BYTES: usize = 20;
30
30
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33
33
34 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
34 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
35 const TREE_METADATA_SIZE: usize = 44;
35 const TREE_METADATA_SIZE: usize = 44;
36 const NODE_SIZE: usize = 44;
36 const NODE_SIZE: usize = 44;
37
37
38 /// Make sure that size-affecting changes are made knowingly
38 /// Make sure that size-affecting changes are made knowingly
39 #[allow(unused)]
39 #[allow(unused)]
40 fn static_assert_size_of() {
40 fn static_assert_size_of() {
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
44 }
44 }
45
45
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
47 #[derive(BytesCast)]
47 #[derive(BytesCast)]
48 #[repr(C)]
48 #[repr(C)]
49 struct DocketHeader {
49 struct DocketHeader {
50 marker: [u8; V2_FORMAT_MARKER.len()],
50 marker: [u8; V2_FORMAT_MARKER.len()],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
53
53
54 metadata: TreeMetadata,
54 metadata: TreeMetadata,
55
55
56 /// Counted in bytes
56 /// Counted in bytes
57 data_size: Size,
57 data_size: Size,
58
58
59 uuid_size: u8,
59 uuid_size: u8,
60 }
60 }
61
61
62 pub struct Docket<'on_disk> {
62 pub struct Docket<'on_disk> {
63 header: &'on_disk DocketHeader,
63 header: &'on_disk DocketHeader,
64 uuid: &'on_disk [u8],
64 uuid: &'on_disk [u8],
65 }
65 }
66
66
67 /// Fields are documented in the *Tree metadata in the docket file*
67 /// Fields are documented in the *Tree metadata in the docket file*
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
69 #[derive(BytesCast)]
69 #[derive(BytesCast)]
70 #[repr(C)]
70 #[repr(C)]
71 struct TreeMetadata {
71 struct TreeMetadata {
72 root_nodes: ChildNodes,
72 root_nodes: ChildNodes,
73 nodes_with_entry_count: Size,
73 nodes_with_entry_count: Size,
74 nodes_with_copy_source_count: Size,
74 nodes_with_copy_source_count: Size,
75 unreachable_bytes: Size,
75 unreachable_bytes: Size,
76 unused: [u8; 4],
76 unused: [u8; 4],
77
77
78 /// See *Optional hash of ignore patterns* section of
78 /// See *Optional hash of ignore patterns* section of
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
80 ignore_patterns_hash: IgnorePatternsHash,
80 ignore_patterns_hash: IgnorePatternsHash,
81 }
81 }
82
82
83 /// Fields are documented in the *The data file format*
83 /// Fields are documented in the *The data file format*
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
85 #[derive(BytesCast)]
85 #[derive(BytesCast)]
86 #[repr(C)]
86 #[repr(C)]
87 pub(super) struct Node {
87 pub(super) struct Node {
88 full_path: PathSlice,
88 full_path: PathSlice,
89
89
90 /// In bytes from `self.full_path.start`
90 /// In bytes from `self.full_path.start`
91 base_name_start: PathSize,
91 base_name_start: PathSize,
92
92
93 copy_source: OptPathSlice,
93 copy_source: OptPathSlice,
94 children: ChildNodes,
94 children: ChildNodes,
95 pub(super) descendants_with_entry_count: Size,
95 pub(super) descendants_with_entry_count: Size,
96 pub(super) tracked_descendants_count: Size,
96 pub(super) tracked_descendants_count: Size,
97 flags: U16Be,
97 flags: U16Be,
98 size: U32Be,
98 size: U32Be,
99 mtime: PackedTruncatedTimestamp,
99 mtime: PackedTruncatedTimestamp,
100 }
100 }
101
101
102 bitflags! {
102 bitflags! {
103 #[repr(C)]
103 #[repr(C)]
104 struct Flags: u16 {
104 struct Flags: u16 {
105 const WDIR_TRACKED = 1 << 0;
105 const WDIR_TRACKED = 1 << 0;
106 const P1_TRACKED = 1 << 1;
106 const P1_TRACKED = 1 << 1;
107 const P2_INFO = 1 << 2;
107 const P2_INFO = 1 << 2;
108 const HAS_MODE_AND_SIZE = 1 << 3;
108 const HAS_MODE_AND_SIZE = 1 << 3;
109 const HAS_FILE_MTIME = 1 << 4;
109 const HAS_FILE_MTIME = 1 << 4;
110 const HAS_DIRECTORY_MTIME = 1 << 5;
110 const HAS_DIRECTORY_MTIME = 1 << 5;
111 const MODE_EXEC_PERM = 1 << 6;
111 const MODE_EXEC_PERM = 1 << 6;
112 const MODE_IS_SYMLINK = 1 << 7;
112 const MODE_IS_SYMLINK = 1 << 7;
113 const EXPECTED_STATE_IS_MODIFIED = 1 << 8;
113 const EXPECTED_STATE_IS_MODIFIED = 1 << 8;
114 const ALL_UNKNOWN_RECORDED = 1 << 9;
114 const ALL_UNKNOWN_RECORDED = 1 << 9;
115 const ALL_IGNORED_RECORDED = 1 << 10;
115 const ALL_IGNORED_RECORDED = 1 << 10;
116 }
116 }
117 }
117 }
118
118
119 /// Duration since the Unix epoch
119 /// Duration since the Unix epoch
120 #[derive(BytesCast, Copy, Clone)]
120 #[derive(BytesCast, Copy, Clone)]
121 #[repr(C)]
121 #[repr(C)]
122 struct PackedTruncatedTimestamp {
122 struct PackedTruncatedTimestamp {
123 truncated_seconds: U32Be,
123 truncated_seconds: U32Be,
124 nanoseconds: U32Be,
124 nanoseconds: U32Be,
125 }
125 }
126
126
127 /// Counted in bytes from the start of the file
127 /// Counted in bytes from the start of the file
128 ///
128 ///
129 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
129 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
130 type Offset = U32Be;
130 type Offset = U32Be;
131
131
132 /// Counted in number of items
132 /// Counted in number of items
133 ///
133 ///
134 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
134 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
135 type Size = U32Be;
135 type Size = U32Be;
136
136
137 /// Counted in bytes
137 /// Counted in bytes
138 ///
138 ///
139 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
139 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
140 type PathSize = U16Be;
140 type PathSize = U16Be;
141
141
142 /// A contiguous sequence of `len` times `Node`, representing the child nodes
142 /// A contiguous sequence of `len` times `Node`, representing the child nodes
143 /// of either some other node or of the repository root.
143 /// of either some other node or of the repository root.
144 ///
144 ///
145 /// Always sorted by ascending `full_path`, to allow binary search.
145 /// Always sorted by ascending `full_path`, to allow binary search.
146 /// Since nodes with the same parent nodes also have the same parent path,
146 /// Since nodes with the same parent nodes also have the same parent path,
147 /// only the `base_name`s need to be compared during binary search.
147 /// only the `base_name`s need to be compared during binary search.
148 #[derive(BytesCast, Copy, Clone)]
148 #[derive(BytesCast, Copy, Clone)]
149 #[repr(C)]
149 #[repr(C)]
150 struct ChildNodes {
150 struct ChildNodes {
151 start: Offset,
151 start: Offset,
152 len: Size,
152 len: Size,
153 }
153 }
154
154
155 /// A `HgPath` of `len` bytes
155 /// A `HgPath` of `len` bytes
156 #[derive(BytesCast, Copy, Clone)]
156 #[derive(BytesCast, Copy, Clone)]
157 #[repr(C)]
157 #[repr(C)]
158 struct PathSlice {
158 struct PathSlice {
159 start: Offset,
159 start: Offset,
160 len: PathSize,
160 len: PathSize,
161 }
161 }
162
162
163 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
163 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
164 type OptPathSlice = PathSlice;
164 type OptPathSlice = PathSlice;
165
165
166 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
166 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
167 ///
167 ///
168 /// This should only happen if Mercurial is buggy or a repository is corrupted.
168 /// This should only happen if Mercurial is buggy or a repository is corrupted.
169 #[derive(Debug)]
169 #[derive(Debug)]
170 pub struct DirstateV2ParseError;
170 pub struct DirstateV2ParseError;
171
171
172 impl From<DirstateV2ParseError> for HgError {
172 impl From<DirstateV2ParseError> for HgError {
173 fn from(_: DirstateV2ParseError) -> Self {
173 fn from(_: DirstateV2ParseError) -> Self {
174 HgError::corrupted("dirstate-v2 parse error")
174 HgError::corrupted("dirstate-v2 parse error")
175 }
175 }
176 }
176 }
177
177
178 impl From<DirstateV2ParseError> for crate::DirstateError {
178 impl From<DirstateV2ParseError> for crate::DirstateError {
179 fn from(error: DirstateV2ParseError) -> Self {
179 fn from(error: DirstateV2ParseError) -> Self {
180 HgError::from(error).into()
180 HgError::from(error).into()
181 }
181 }
182 }
182 }
183
183
184 impl<'on_disk> Docket<'on_disk> {
184 impl<'on_disk> Docket<'on_disk> {
185 pub fn parents(&self) -> DirstateParents {
185 pub fn parents(&self) -> DirstateParents {
186 use crate::Node;
186 use crate::Node;
187 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
187 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
188 .unwrap()
188 .unwrap()
189 .clone();
189 .clone();
190 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
190 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
191 .unwrap()
191 .unwrap()
192 .clone();
192 .clone();
193 DirstateParents { p1, p2 }
193 DirstateParents { p1, p2 }
194 }
194 }
195
195
196 pub fn tree_metadata(&self) -> &[u8] {
196 pub fn tree_metadata(&self) -> &[u8] {
197 self.header.metadata.as_bytes()
197 self.header.metadata.as_bytes()
198 }
198 }
199
199
200 pub fn data_size(&self) -> usize {
200 pub fn data_size(&self) -> usize {
201 // This `unwrap` could only panic on a 16-bit CPU
201 // This `unwrap` could only panic on a 16-bit CPU
202 self.header.data_size.get().try_into().unwrap()
202 self.header.data_size.get().try_into().unwrap()
203 }
203 }
204
204
205 pub fn data_filename(&self) -> String {
205 pub fn data_filename(&self) -> String {
206 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
206 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
207 }
207 }
208 }
208 }
209
209
210 pub fn read_docket(
210 pub fn read_docket(
211 on_disk: &[u8],
211 on_disk: &[u8],
212 ) -> Result<Docket<'_>, DirstateV2ParseError> {
212 ) -> Result<Docket<'_>, DirstateV2ParseError> {
213 let (header, uuid) =
213 let (header, uuid) =
214 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
214 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
215 let uuid_size = header.uuid_size as usize;
215 let uuid_size = header.uuid_size as usize;
216 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
216 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
217 Ok(Docket { header, uuid })
217 Ok(Docket { header, uuid })
218 } else {
218 } else {
219 Err(DirstateV2ParseError)
219 Err(DirstateV2ParseError)
220 }
220 }
221 }
221 }
222
222
223 pub(super) fn read<'on_disk>(
223 pub(super) fn read<'on_disk>(
224 on_disk: &'on_disk [u8],
224 on_disk: &'on_disk [u8],
225 metadata: &[u8],
225 metadata: &[u8],
226 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
226 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
227 if on_disk.is_empty() {
227 if on_disk.is_empty() {
228 return Ok(DirstateMap::empty(on_disk));
228 return Ok(DirstateMap::empty(on_disk));
229 }
229 }
230 let (meta, _) = TreeMetadata::from_bytes(metadata)
230 let (meta, _) = TreeMetadata::from_bytes(metadata)
231 .map_err(|_| DirstateV2ParseError)?;
231 .map_err(|_| DirstateV2ParseError)?;
232 let dirstate_map = DirstateMap {
232 let dirstate_map = DirstateMap {
233 on_disk,
233 on_disk,
234 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
234 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
235 on_disk,
235 on_disk,
236 meta.root_nodes,
236 meta.root_nodes,
237 )?),
237 )?),
238 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
238 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
239 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
239 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
240 ignore_patterns_hash: meta.ignore_patterns_hash,
240 ignore_patterns_hash: meta.ignore_patterns_hash,
241 unreachable_bytes: meta.unreachable_bytes.get(),
241 unreachable_bytes: meta.unreachable_bytes.get(),
242 };
242 };
243 Ok(dirstate_map)
243 Ok(dirstate_map)
244 }
244 }
245
245
246 impl Node {
246 impl Node {
247 pub(super) fn full_path<'on_disk>(
247 pub(super) fn full_path<'on_disk>(
248 &self,
248 &self,
249 on_disk: &'on_disk [u8],
249 on_disk: &'on_disk [u8],
250 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
250 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
251 read_hg_path(on_disk, self.full_path)
251 read_hg_path(on_disk, self.full_path)
252 }
252 }
253
253
254 pub(super) fn base_name_start<'on_disk>(
254 pub(super) fn base_name_start<'on_disk>(
255 &self,
255 &self,
256 ) -> Result<usize, DirstateV2ParseError> {
256 ) -> Result<usize, DirstateV2ParseError> {
257 let start = self.base_name_start.get();
257 let start = self.base_name_start.get();
258 if start < self.full_path.len.get() {
258 if start < self.full_path.len.get() {
259 let start = usize::try_from(start)
259 let start = usize::try_from(start)
260 // u32 -> usize, could only panic on a 16-bit CPU
260 // u32 -> usize, could only panic on a 16-bit CPU
261 .expect("dirstate-v2 base_name_start out of bounds");
261 .expect("dirstate-v2 base_name_start out of bounds");
262 Ok(start)
262 Ok(start)
263 } else {
263 } else {
264 Err(DirstateV2ParseError)
264 Err(DirstateV2ParseError)
265 }
265 }
266 }
266 }
267
267
268 pub(super) fn base_name<'on_disk>(
268 pub(super) fn base_name<'on_disk>(
269 &self,
269 &self,
270 on_disk: &'on_disk [u8],
270 on_disk: &'on_disk [u8],
271 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
271 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
272 let full_path = self.full_path(on_disk)?;
272 let full_path = self.full_path(on_disk)?;
273 let base_name_start = self.base_name_start()?;
273 let base_name_start = self.base_name_start()?;
274 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
274 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
275 }
275 }
276
276
277 pub(super) fn path<'on_disk>(
277 pub(super) fn path<'on_disk>(
278 &self,
278 &self,
279 on_disk: &'on_disk [u8],
279 on_disk: &'on_disk [u8],
280 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
280 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
281 Ok(WithBasename::from_raw_parts(
281 Ok(WithBasename::from_raw_parts(
282 Cow::Borrowed(self.full_path(on_disk)?),
282 Cow::Borrowed(self.full_path(on_disk)?),
283 self.base_name_start()?,
283 self.base_name_start()?,
284 ))
284 ))
285 }
285 }
286
286
287 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
287 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
288 self.copy_source.start.get() != 0
288 self.copy_source.start.get() != 0
289 }
289 }
290
290
291 pub(super) fn copy_source<'on_disk>(
291 pub(super) fn copy_source<'on_disk>(
292 &self,
292 &self,
293 on_disk: &'on_disk [u8],
293 on_disk: &'on_disk [u8],
294 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
294 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
295 Ok(if self.has_copy_source() {
295 Ok(if self.has_copy_source() {
296 Some(read_hg_path(on_disk, self.copy_source)?)
296 Some(read_hg_path(on_disk, self.copy_source)?)
297 } else {
297 } else {
298 None
298 None
299 })
299 })
300 }
300 }
301
301
302 fn flags(&self) -> Flags {
302 fn flags(&self) -> Flags {
303 Flags::from_bits_truncate(self.flags.get())
303 Flags::from_bits_truncate(self.flags.get())
304 }
304 }
305
305
306 fn has_entry(&self) -> bool {
306 fn has_entry(&self) -> bool {
307 self.flags().intersects(
307 self.flags().intersects(
308 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
308 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
309 )
309 )
310 }
310 }
311
311
312 pub(super) fn node_data(
312 pub(super) fn node_data(
313 &self,
313 &self,
314 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
314 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
315 if self.has_entry() {
315 if self.has_entry() {
316 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
316 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
317 } else if let Some(mtime) = self.cached_directory_mtime()? {
317 } else if let Some(mtime) = self.cached_directory_mtime()? {
318 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
318 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
319 } else {
319 } else {
320 Ok(dirstate_map::NodeData::None)
320 Ok(dirstate_map::NodeData::None)
321 }
321 }
322 }
322 }
323
323
324 pub(super) fn cached_directory_mtime(
324 pub(super) fn cached_directory_mtime(
325 &self,
325 &self,
326 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
326 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
327 // For now we do not have code to handle ALL_UNKNOWN_RECORDED, so we
327 // For now we do not have code to handle ALL_UNKNOWN_RECORDED, so we
328 // ignore the mtime if the flag is set.
328 // ignore the mtime if the flag is set.
329 if self.flags().contains(Flags::HAS_DIRECTORY_MTIME)
329 if self.flags().contains(Flags::HAS_DIRECTORY_MTIME)
330 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
330 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
331 {
331 {
332 if self.flags().contains(Flags::HAS_FILE_MTIME) {
332 if self.flags().contains(Flags::HAS_FILE_MTIME) {
333 Err(DirstateV2ParseError)
333 Err(DirstateV2ParseError)
334 } else {
334 } else {
335 Ok(Some(self.mtime.try_into()?))
335 Ok(Some(self.mtime.try_into()?))
336 }
336 }
337 } else {
337 } else {
338 Ok(None)
338 Ok(None)
339 }
339 }
340 }
340 }
341
341
342 fn synthesize_unix_mode(&self) -> u32 {
342 fn synthesize_unix_mode(&self) -> u32 {
343 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
343 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
344 libc::S_IFLNK
344 libc::S_IFLNK
345 } else {
345 } else {
346 libc::S_IFREG
346 libc::S_IFREG
347 };
347 };
348 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
348 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
349 0o755
349 0o755
350 } else {
350 } else {
351 0o644
351 0o644
352 };
352 };
353 file_type | permisions
353 file_type | permisions
354 }
354 }
355
355
356 fn assume_entry(&self) -> DirstateEntry {
356 fn assume_entry(&self) -> DirstateEntry {
357 // TODO: convert through raw bits instead?
357 // TODO: convert through raw bits instead?
358 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
358 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
359 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
359 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
360 let p2_info = self.flags().contains(Flags::P2_INFO);
360 let p2_info = self.flags().contains(Flags::P2_INFO);
361 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
361 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
362 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
362 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
363 {
363 {
364 Some((self.synthesize_unix_mode(), self.size.into()))
364 Some((self.synthesize_unix_mode(), self.size.into()))
365 } else {
365 } else {
366 None
366 None
367 };
367 };
368 let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME)
368 let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME)
369 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
369 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
370 {
370 {
371 Some(self.mtime.truncated_seconds.into())
371 Some(self.mtime.truncated_seconds.into())
372 } else {
372 } else {
373 None
373 None
374 };
374 };
375 DirstateEntry::from_v2_data(
375 DirstateEntry::from_v2_data(
376 wdir_tracked,
376 wdir_tracked,
377 p1_tracked,
377 p1_tracked,
378 p2_info,
378 p2_info,
379 mode_size,
379 mode_size,
380 mtime,
380 mtime,
381 None,
382 None,
381 )
383 )
382 }
384 }
383
385
384 pub(super) fn entry(
386 pub(super) fn entry(
385 &self,
387 &self,
386 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
388 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
387 if self.has_entry() {
389 if self.has_entry() {
388 Ok(Some(self.assume_entry()))
390 Ok(Some(self.assume_entry()))
389 } else {
391 } else {
390 Ok(None)
392 Ok(None)
391 }
393 }
392 }
394 }
393
395
394 pub(super) fn children<'on_disk>(
396 pub(super) fn children<'on_disk>(
395 &self,
397 &self,
396 on_disk: &'on_disk [u8],
398 on_disk: &'on_disk [u8],
397 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
399 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
398 read_nodes(on_disk, self.children)
400 read_nodes(on_disk, self.children)
399 }
401 }
400
402
401 pub(super) fn to_in_memory_node<'on_disk>(
403 pub(super) fn to_in_memory_node<'on_disk>(
402 &self,
404 &self,
403 on_disk: &'on_disk [u8],
405 on_disk: &'on_disk [u8],
404 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
406 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
405 Ok(dirstate_map::Node {
407 Ok(dirstate_map::Node {
406 children: dirstate_map::ChildNodes::OnDisk(
408 children: dirstate_map::ChildNodes::OnDisk(
407 self.children(on_disk)?,
409 self.children(on_disk)?,
408 ),
410 ),
409 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
411 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
410 data: self.node_data()?,
412 data: self.node_data()?,
411 descendants_with_entry_count: self
413 descendants_with_entry_count: self
412 .descendants_with_entry_count
414 .descendants_with_entry_count
413 .get(),
415 .get(),
414 tracked_descendants_count: self.tracked_descendants_count.get(),
416 tracked_descendants_count: self.tracked_descendants_count.get(),
415 })
417 })
416 }
418 }
417
419
418 fn from_dirstate_entry(
420 fn from_dirstate_entry(
419 entry: &DirstateEntry,
421 entry: &DirstateEntry,
420 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
422 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
421 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
423 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
422 entry.v2_data();
424 entry.v2_data();
423 // TODO: convert throug raw flag bits instead?
425 // TODO: convert throug raw flag bits instead?
424 let mut flags = Flags::empty();
426 let mut flags = Flags::empty();
425 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
427 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
426 flags.set(Flags::P1_TRACKED, p1_tracked);
428 flags.set(Flags::P1_TRACKED, p1_tracked);
427 flags.set(Flags::P2_INFO, p2_info);
429 flags.set(Flags::P2_INFO, p2_info);
428 let size = if let Some((m, s)) = mode_size_opt {
430 let size = if let Some((m, s)) = mode_size_opt {
429 let exec_perm = m & libc::S_IXUSR != 0;
431 let exec_perm = m & libc::S_IXUSR != 0;
430 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
432 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
431 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
433 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
432 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
434 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
433 flags.insert(Flags::HAS_MODE_AND_SIZE);
435 flags.insert(Flags::HAS_MODE_AND_SIZE);
434 s.into()
436 s.into()
435 } else {
437 } else {
436 0.into()
438 0.into()
437 };
439 };
438 let mtime = if let Some(m) = mtime_opt {
440 let mtime = if let Some(m) = mtime_opt {
439 flags.insert(Flags::HAS_FILE_MTIME);
441 flags.insert(Flags::HAS_FILE_MTIME);
440 PackedTruncatedTimestamp {
442 PackedTruncatedTimestamp {
441 truncated_seconds: m.into(),
443 truncated_seconds: m.into(),
442 nanoseconds: 0.into(),
444 nanoseconds: 0.into(),
443 }
445 }
444 } else {
446 } else {
445 PackedTruncatedTimestamp::null()
447 PackedTruncatedTimestamp::null()
446 };
448 };
447 (flags, size, mtime)
449 (flags, size, mtime)
448 }
450 }
449 }
451 }
450
452
451 fn read_hg_path(
453 fn read_hg_path(
452 on_disk: &[u8],
454 on_disk: &[u8],
453 slice: PathSlice,
455 slice: PathSlice,
454 ) -> Result<&HgPath, DirstateV2ParseError> {
456 ) -> Result<&HgPath, DirstateV2ParseError> {
455 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
457 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
456 }
458 }
457
459
458 fn read_nodes(
460 fn read_nodes(
459 on_disk: &[u8],
461 on_disk: &[u8],
460 slice: ChildNodes,
462 slice: ChildNodes,
461 ) -> Result<&[Node], DirstateV2ParseError> {
463 ) -> Result<&[Node], DirstateV2ParseError> {
462 read_slice(on_disk, slice.start, slice.len.get())
464 read_slice(on_disk, slice.start, slice.len.get())
463 }
465 }
464
466
465 fn read_slice<T, Len>(
467 fn read_slice<T, Len>(
466 on_disk: &[u8],
468 on_disk: &[u8],
467 start: Offset,
469 start: Offset,
468 len: Len,
470 len: Len,
469 ) -> Result<&[T], DirstateV2ParseError>
471 ) -> Result<&[T], DirstateV2ParseError>
470 where
472 where
471 T: BytesCast,
473 T: BytesCast,
472 Len: TryInto<usize>,
474 Len: TryInto<usize>,
473 {
475 {
474 // Either `usize::MAX` would result in "out of bounds" error since a single
476 // Either `usize::MAX` would result in "out of bounds" error since a single
475 // `&[u8]` cannot occupy the entire addess space.
477 // `&[u8]` cannot occupy the entire addess space.
476 let start = start.get().try_into().unwrap_or(std::usize::MAX);
478 let start = start.get().try_into().unwrap_or(std::usize::MAX);
477 let len = len.try_into().unwrap_or(std::usize::MAX);
479 let len = len.try_into().unwrap_or(std::usize::MAX);
478 on_disk
480 on_disk
479 .get(start..)
481 .get(start..)
480 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
482 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
481 .map(|(slice, _rest)| slice)
483 .map(|(slice, _rest)| slice)
482 .ok_or_else(|| DirstateV2ParseError)
484 .ok_or_else(|| DirstateV2ParseError)
483 }
485 }
484
486
485 pub(crate) fn for_each_tracked_path<'on_disk>(
487 pub(crate) fn for_each_tracked_path<'on_disk>(
486 on_disk: &'on_disk [u8],
488 on_disk: &'on_disk [u8],
487 metadata: &[u8],
489 metadata: &[u8],
488 mut f: impl FnMut(&'on_disk HgPath),
490 mut f: impl FnMut(&'on_disk HgPath),
489 ) -> Result<(), DirstateV2ParseError> {
491 ) -> Result<(), DirstateV2ParseError> {
490 let (meta, _) = TreeMetadata::from_bytes(metadata)
492 let (meta, _) = TreeMetadata::from_bytes(metadata)
491 .map_err(|_| DirstateV2ParseError)?;
493 .map_err(|_| DirstateV2ParseError)?;
492 fn recur<'on_disk>(
494 fn recur<'on_disk>(
493 on_disk: &'on_disk [u8],
495 on_disk: &'on_disk [u8],
494 nodes: ChildNodes,
496 nodes: ChildNodes,
495 f: &mut impl FnMut(&'on_disk HgPath),
497 f: &mut impl FnMut(&'on_disk HgPath),
496 ) -> Result<(), DirstateV2ParseError> {
498 ) -> Result<(), DirstateV2ParseError> {
497 for node in read_nodes(on_disk, nodes)? {
499 for node in read_nodes(on_disk, nodes)? {
498 if let Some(entry) = node.entry()? {
500 if let Some(entry) = node.entry()? {
499 if entry.state().is_tracked() {
501 if entry.state().is_tracked() {
500 f(node.full_path(on_disk)?)
502 f(node.full_path(on_disk)?)
501 }
503 }
502 }
504 }
503 recur(on_disk, node.children, f)?
505 recur(on_disk, node.children, f)?
504 }
506 }
505 Ok(())
507 Ok(())
506 }
508 }
507 recur(on_disk, meta.root_nodes, &mut f)
509 recur(on_disk, meta.root_nodes, &mut f)
508 }
510 }
509
511
510 /// Returns new data and metadata, together with whether that data should be
512 /// Returns new data and metadata, together with whether that data should be
511 /// appended to the existing data file whose content is at
513 /// appended to the existing data file whose content is at
512 /// `dirstate_map.on_disk` (true), instead of written to a new data file
514 /// `dirstate_map.on_disk` (true), instead of written to a new data file
513 /// (false).
515 /// (false).
514 pub(super) fn write(
516 pub(super) fn write(
515 dirstate_map: &mut DirstateMap,
517 dirstate_map: &mut DirstateMap,
516 can_append: bool,
518 can_append: bool,
517 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
519 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
518 let append = can_append && dirstate_map.write_should_append();
520 let append = can_append && dirstate_map.write_should_append();
519
521
520 // This ignores the space for paths, and for nodes without an entry.
522 // This ignores the space for paths, and for nodes without an entry.
521 // TODO: better estimate? Skip the `Vec` and write to a file directly?
523 // TODO: better estimate? Skip the `Vec` and write to a file directly?
522 let size_guess = std::mem::size_of::<Node>()
524 let size_guess = std::mem::size_of::<Node>()
523 * dirstate_map.nodes_with_entry_count as usize;
525 * dirstate_map.nodes_with_entry_count as usize;
524
526
525 let mut writer = Writer {
527 let mut writer = Writer {
526 dirstate_map,
528 dirstate_map,
527 append,
529 append,
528 out: Vec::with_capacity(size_guess),
530 out: Vec::with_capacity(size_guess),
529 };
531 };
530
532
531 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
533 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
532
534
533 let meta = TreeMetadata {
535 let meta = TreeMetadata {
534 root_nodes,
536 root_nodes,
535 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
537 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
536 nodes_with_copy_source_count: dirstate_map
538 nodes_with_copy_source_count: dirstate_map
537 .nodes_with_copy_source_count
539 .nodes_with_copy_source_count
538 .into(),
540 .into(),
539 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
541 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
540 unused: [0; 4],
542 unused: [0; 4],
541 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
543 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
542 };
544 };
543 Ok((writer.out, meta.as_bytes().to_vec(), append))
545 Ok((writer.out, meta.as_bytes().to_vec(), append))
544 }
546 }
545
547
546 struct Writer<'dmap, 'on_disk> {
548 struct Writer<'dmap, 'on_disk> {
547 dirstate_map: &'dmap DirstateMap<'on_disk>,
549 dirstate_map: &'dmap DirstateMap<'on_disk>,
548 append: bool,
550 append: bool,
549 out: Vec<u8>,
551 out: Vec<u8>,
550 }
552 }
551
553
552 impl Writer<'_, '_> {
554 impl Writer<'_, '_> {
553 fn write_nodes(
555 fn write_nodes(
554 &mut self,
556 &mut self,
555 nodes: dirstate_map::ChildNodesRef,
557 nodes: dirstate_map::ChildNodesRef,
556 ) -> Result<ChildNodes, DirstateError> {
558 ) -> Result<ChildNodes, DirstateError> {
557 // Reuse already-written nodes if possible
559 // Reuse already-written nodes if possible
558 if self.append {
560 if self.append {
559 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
561 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
560 let start = self.on_disk_offset_of(nodes_slice).expect(
562 let start = self.on_disk_offset_of(nodes_slice).expect(
561 "dirstate-v2 OnDisk nodes not found within on_disk",
563 "dirstate-v2 OnDisk nodes not found within on_disk",
562 );
564 );
563 let len = child_nodes_len_from_usize(nodes_slice.len());
565 let len = child_nodes_len_from_usize(nodes_slice.len());
564 return Ok(ChildNodes { start, len });
566 return Ok(ChildNodes { start, len });
565 }
567 }
566 }
568 }
567
569
568 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
570 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
569 // undefined iteration order. Sort to enable binary search in the
571 // undefined iteration order. Sort to enable binary search in the
570 // written file.
572 // written file.
571 let nodes = nodes.sorted();
573 let nodes = nodes.sorted();
572 let nodes_len = nodes.len();
574 let nodes_len = nodes.len();
573
575
574 // First accumulate serialized nodes in a `Vec`
576 // First accumulate serialized nodes in a `Vec`
575 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
577 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
576 for node in nodes {
578 for node in nodes {
577 let children =
579 let children =
578 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
580 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
579 let full_path = node.full_path(self.dirstate_map.on_disk)?;
581 let full_path = node.full_path(self.dirstate_map.on_disk)?;
580 let full_path = self.write_path(full_path.as_bytes());
582 let full_path = self.write_path(full_path.as_bytes());
581 let copy_source = if let Some(source) =
583 let copy_source = if let Some(source) =
582 node.copy_source(self.dirstate_map.on_disk)?
584 node.copy_source(self.dirstate_map.on_disk)?
583 {
585 {
584 self.write_path(source.as_bytes())
586 self.write_path(source.as_bytes())
585 } else {
587 } else {
586 PathSlice {
588 PathSlice {
587 start: 0.into(),
589 start: 0.into(),
588 len: 0.into(),
590 len: 0.into(),
589 }
591 }
590 };
592 };
591 on_disk_nodes.push(match node {
593 on_disk_nodes.push(match node {
592 NodeRef::InMemory(path, node) => {
594 NodeRef::InMemory(path, node) => {
593 let (flags, size, mtime) = match &node.data {
595 let (flags, size, mtime) = match &node.data {
594 dirstate_map::NodeData::Entry(entry) => {
596 dirstate_map::NodeData::Entry(entry) => {
595 Node::from_dirstate_entry(entry)
597 Node::from_dirstate_entry(entry)
596 }
598 }
597 dirstate_map::NodeData::CachedDirectory { mtime } => (
599 dirstate_map::NodeData::CachedDirectory { mtime } => (
598 // we currently never set a mtime if unknown file
600 // we currently never set a mtime if unknown file
599 // are present.
601 // are present.
600 // So if we have a mtime for a directory, we know
602 // So if we have a mtime for a directory, we know
601 // they are no unknown
603 // they are no unknown
602 // files and we
604 // files and we
603 // blindly set ALL_UNKNOWN_RECORDED.
605 // blindly set ALL_UNKNOWN_RECORDED.
604 //
606 //
605 // We never set ALL_IGNORED_RECORDED since we
607 // We never set ALL_IGNORED_RECORDED since we
606 // don't track that case
608 // don't track that case
607 // currently.
609 // currently.
608 Flags::HAS_DIRECTORY_MTIME
610 Flags::HAS_DIRECTORY_MTIME
609 | Flags::ALL_UNKNOWN_RECORDED,
611 | Flags::ALL_UNKNOWN_RECORDED,
610 0.into(),
612 0.into(),
611 (*mtime).into(),
613 (*mtime).into(),
612 ),
614 ),
613 dirstate_map::NodeData::None => (
615 dirstate_map::NodeData::None => (
614 Flags::empty(),
616 Flags::empty(),
615 0.into(),
617 0.into(),
616 PackedTruncatedTimestamp::null(),
618 PackedTruncatedTimestamp::null(),
617 ),
619 ),
618 };
620 };
619 Node {
621 Node {
620 children,
622 children,
621 copy_source,
623 copy_source,
622 full_path,
624 full_path,
623 base_name_start: u16::try_from(path.base_name_start())
625 base_name_start: u16::try_from(path.base_name_start())
624 // Could only panic for paths over 64 KiB
626 // Could only panic for paths over 64 KiB
625 .expect("dirstate-v2 path length overflow")
627 .expect("dirstate-v2 path length overflow")
626 .into(),
628 .into(),
627 descendants_with_entry_count: node
629 descendants_with_entry_count: node
628 .descendants_with_entry_count
630 .descendants_with_entry_count
629 .into(),
631 .into(),
630 tracked_descendants_count: node
632 tracked_descendants_count: node
631 .tracked_descendants_count
633 .tracked_descendants_count
632 .into(),
634 .into(),
633 flags: flags.bits().into(),
635 flags: flags.bits().into(),
634 size,
636 size,
635 mtime,
637 mtime,
636 }
638 }
637 }
639 }
638 NodeRef::OnDisk(node) => Node {
640 NodeRef::OnDisk(node) => Node {
639 children,
641 children,
640 copy_source,
642 copy_source,
641 full_path,
643 full_path,
642 ..*node
644 ..*node
643 },
645 },
644 })
646 })
645 }
647 }
646 // … so we can write them contiguously, after writing everything else
648 // … so we can write them contiguously, after writing everything else
647 // they refer to.
649 // they refer to.
648 let start = self.current_offset();
650 let start = self.current_offset();
649 let len = child_nodes_len_from_usize(nodes_len);
651 let len = child_nodes_len_from_usize(nodes_len);
650 self.out.extend(on_disk_nodes.as_bytes());
652 self.out.extend(on_disk_nodes.as_bytes());
651 Ok(ChildNodes { start, len })
653 Ok(ChildNodes { start, len })
652 }
654 }
653
655
654 /// If the given slice of items is within `on_disk`, returns its offset
656 /// If the given slice of items is within `on_disk`, returns its offset
655 /// from the start of `on_disk`.
657 /// from the start of `on_disk`.
656 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
658 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
657 where
659 where
658 T: BytesCast,
660 T: BytesCast,
659 {
661 {
660 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
662 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
661 let start = slice.as_ptr() as usize;
663 let start = slice.as_ptr() as usize;
662 let end = start + slice.len();
664 let end = start + slice.len();
663 start..=end
665 start..=end
664 }
666 }
665 let slice_addresses = address_range(slice.as_bytes());
667 let slice_addresses = address_range(slice.as_bytes());
666 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
668 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
667 if on_disk_addresses.contains(slice_addresses.start())
669 if on_disk_addresses.contains(slice_addresses.start())
668 && on_disk_addresses.contains(slice_addresses.end())
670 && on_disk_addresses.contains(slice_addresses.end())
669 {
671 {
670 let offset = slice_addresses.start() - on_disk_addresses.start();
672 let offset = slice_addresses.start() - on_disk_addresses.start();
671 Some(offset_from_usize(offset))
673 Some(offset_from_usize(offset))
672 } else {
674 } else {
673 None
675 None
674 }
676 }
675 }
677 }
676
678
677 fn current_offset(&mut self) -> Offset {
679 fn current_offset(&mut self) -> Offset {
678 let mut offset = self.out.len();
680 let mut offset = self.out.len();
679 if self.append {
681 if self.append {
680 offset += self.dirstate_map.on_disk.len()
682 offset += self.dirstate_map.on_disk.len()
681 }
683 }
682 offset_from_usize(offset)
684 offset_from_usize(offset)
683 }
685 }
684
686
685 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
687 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
686 let len = path_len_from_usize(slice.len());
688 let len = path_len_from_usize(slice.len());
687 // Reuse an already-written path if possible
689 // Reuse an already-written path if possible
688 if self.append {
690 if self.append {
689 if let Some(start) = self.on_disk_offset_of(slice) {
691 if let Some(start) = self.on_disk_offset_of(slice) {
690 return PathSlice { start, len };
692 return PathSlice { start, len };
691 }
693 }
692 }
694 }
693 let start = self.current_offset();
695 let start = self.current_offset();
694 self.out.extend(slice.as_bytes());
696 self.out.extend(slice.as_bytes());
695 PathSlice { start, len }
697 PathSlice { start, len }
696 }
698 }
697 }
699 }
698
700
699 fn offset_from_usize(x: usize) -> Offset {
701 fn offset_from_usize(x: usize) -> Offset {
700 u32::try_from(x)
702 u32::try_from(x)
701 // Could only panic for a dirstate file larger than 4 GiB
703 // Could only panic for a dirstate file larger than 4 GiB
702 .expect("dirstate-v2 offset overflow")
704 .expect("dirstate-v2 offset overflow")
703 .into()
705 .into()
704 }
706 }
705
707
706 fn child_nodes_len_from_usize(x: usize) -> Size {
708 fn child_nodes_len_from_usize(x: usize) -> Size {
707 u32::try_from(x)
709 u32::try_from(x)
708 // Could only panic with over 4 billion nodes
710 // Could only panic with over 4 billion nodes
709 .expect("dirstate-v2 slice length overflow")
711 .expect("dirstate-v2 slice length overflow")
710 .into()
712 .into()
711 }
713 }
712
714
713 fn path_len_from_usize(x: usize) -> PathSize {
715 fn path_len_from_usize(x: usize) -> PathSize {
714 u16::try_from(x)
716 u16::try_from(x)
715 // Could only panic for paths over 64 KiB
717 // Could only panic for paths over 64 KiB
716 .expect("dirstate-v2 path length overflow")
718 .expect("dirstate-v2 path length overflow")
717 .into()
719 .into()
718 }
720 }
719
721
720 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
722 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
721 fn from(timestamp: TruncatedTimestamp) -> Self {
723 fn from(timestamp: TruncatedTimestamp) -> Self {
722 Self {
724 Self {
723 truncated_seconds: timestamp.truncated_seconds().into(),
725 truncated_seconds: timestamp.truncated_seconds().into(),
724 nanoseconds: timestamp.nanoseconds().into(),
726 nanoseconds: timestamp.nanoseconds().into(),
725 }
727 }
726 }
728 }
727 }
729 }
728
730
729 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
731 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
730 type Error = DirstateV2ParseError;
732 type Error = DirstateV2ParseError;
731
733
732 fn try_from(
734 fn try_from(
733 timestamp: PackedTruncatedTimestamp,
735 timestamp: PackedTruncatedTimestamp,
734 ) -> Result<Self, Self::Error> {
736 ) -> Result<Self, Self::Error> {
735 Self::from_already_truncated(
737 Self::from_already_truncated(
736 timestamp.truncated_seconds.get(),
738 timestamp.truncated_seconds.get(),
737 timestamp.nanoseconds.get(),
739 timestamp.nanoseconds.get(),
738 )
740 )
739 }
741 }
740 }
742 }
741 impl PackedTruncatedTimestamp {
743 impl PackedTruncatedTimestamp {
742 fn null() -> Self {
744 fn null() -> Self {
743 Self {
745 Self {
744 truncated_seconds: 0.into(),
746 truncated_seconds: 0.into(),
745 nanoseconds: 0.into(),
747 nanoseconds: 0.into(),
746 }
748 }
747 }
749 }
748 }
750 }
@@ -1,255 +1,263 b''
1 use cpython::exc;
1 use cpython::exc;
2 use cpython::ObjectProtocol;
2 use cpython::ObjectProtocol;
3 use cpython::PyBytes;
3 use cpython::PyBytes;
4 use cpython::PyErr;
4 use cpython::PyErr;
5 use cpython::PyNone;
5 use cpython::PyNone;
6 use cpython::PyObject;
6 use cpython::PyObject;
7 use cpython::PyResult;
7 use cpython::PyResult;
8 use cpython::Python;
8 use cpython::Python;
9 use cpython::PythonObject;
9 use cpython::PythonObject;
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_info: bool = false,
22 p2_info: bool = false,
23 has_meaningful_data: bool = true,
23 has_meaningful_data: bool = true,
24 has_meaningful_mtime: bool = true,
24 has_meaningful_mtime: bool = true,
25 parentfiledata: Option<(u32, u32, u32)> = None,
25 parentfiledata: Option<(u32, u32, u32)> = None,
26 fallback_exec: Option<bool> = None,
27 fallback_symlink: Option<bool> = None,
26
28
27 ) -> PyResult<DirstateItem> {
29 ) -> PyResult<DirstateItem> {
28 let mut mode_size_opt = None;
30 let mut mode_size_opt = None;
29 let mut mtime_opt = None;
31 let mut mtime_opt = None;
30 if let Some((mode, size, mtime)) = parentfiledata {
32 if let Some((mode, size, mtime)) = parentfiledata {
31 if has_meaningful_data {
33 if has_meaningful_data {
32 mode_size_opt = Some((mode, size))
34 mode_size_opt = Some((mode, size))
33 }
35 }
34 if has_meaningful_mtime {
36 if has_meaningful_mtime {
35 mtime_opt = Some(mtime)
37 mtime_opt = Some(mtime)
36 }
38 }
37 }
39 }
38 let entry = DirstateEntry::from_v2_data(
40 let entry = DirstateEntry::from_v2_data(
39 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
41 wc_tracked,
42 p1_tracked,
43 p2_info,
44 mode_size_opt,
45 mtime_opt,
46 fallback_exec,
47 fallback_symlink,
40 );
48 );
41 DirstateItem::create_instance(py, Cell::new(entry))
49 DirstateItem::create_instance(py, Cell::new(entry))
42 }
50 }
43
51
44 @property
52 @property
45 def state(&self) -> PyResult<PyBytes> {
53 def state(&self) -> PyResult<PyBytes> {
46 let state_byte: u8 = self.entry(py).get().state().into();
54 let state_byte: u8 = self.entry(py).get().state().into();
47 Ok(PyBytes::new(py, &[state_byte]))
55 Ok(PyBytes::new(py, &[state_byte]))
48 }
56 }
49
57
50 @property
58 @property
51 def mode(&self) -> PyResult<i32> {
59 def mode(&self) -> PyResult<i32> {
52 Ok(self.entry(py).get().mode())
60 Ok(self.entry(py).get().mode())
53 }
61 }
54
62
55 @property
63 @property
56 def size(&self) -> PyResult<i32> {
64 def size(&self) -> PyResult<i32> {
57 Ok(self.entry(py).get().size())
65 Ok(self.entry(py).get().size())
58 }
66 }
59
67
60 @property
68 @property
61 def mtime(&self) -> PyResult<i32> {
69 def mtime(&self) -> PyResult<i32> {
62 Ok(self.entry(py).get().mtime())
70 Ok(self.entry(py).get().mtime())
63 }
71 }
64
72
65 @property
73 @property
66 def has_fallback_exec(&self) -> PyResult<bool> {
74 def has_fallback_exec(&self) -> PyResult<bool> {
67 match self.entry(py).get().get_fallback_exec() {
75 match self.entry(py).get().get_fallback_exec() {
68 Some(_) => Ok(true),
76 Some(_) => Ok(true),
69 None => Ok(false),
77 None => Ok(false),
70 }
78 }
71 }
79 }
72
80
73 @property
81 @property
74 def fallback_exec(&self) -> PyResult<Option<bool>> {
82 def fallback_exec(&self) -> PyResult<Option<bool>> {
75 match self.entry(py).get().get_fallback_exec() {
83 match self.entry(py).get().get_fallback_exec() {
76 Some(exec) => Ok(Some(exec)),
84 Some(exec) => Ok(Some(exec)),
77 None => Ok(None),
85 None => Ok(None),
78 }
86 }
79 }
87 }
80
88
81 @fallback_exec.setter
89 @fallback_exec.setter
82 def set_fallback_exec(&self, value: Option<PyObject>) -> PyResult<()> {
90 def set_fallback_exec(&self, value: Option<PyObject>) -> PyResult<()> {
83 match value {
91 match value {
84 None => {self.entry(py).get().set_fallback_exec(None);},
92 None => {self.entry(py).get().set_fallback_exec(None);},
85 Some(value) => {
93 Some(value) => {
86 if value.is_none(py) {
94 if value.is_none(py) {
87 self.entry(py).get().set_fallback_exec(None);
95 self.entry(py).get().set_fallback_exec(None);
88 } else {
96 } else {
89 self.entry(py).get().set_fallback_exec(
97 self.entry(py).get().set_fallback_exec(
90 Some(value.is_true(py)?)
98 Some(value.is_true(py)?)
91 );
99 );
92 }},
100 }},
93 }
101 }
94 Ok(())
102 Ok(())
95 }
103 }
96
104
97 @property
105 @property
98 def has_fallback_symlink(&self) -> PyResult<bool> {
106 def has_fallback_symlink(&self) -> PyResult<bool> {
99 match self.entry(py).get().get_fallback_symlink() {
107 match self.entry(py).get().get_fallback_symlink() {
100 Some(_) => Ok(true),
108 Some(_) => Ok(true),
101 None => Ok(false),
109 None => Ok(false),
102 }
110 }
103 }
111 }
104
112
105 @property
113 @property
106 def fallback_symlink(&self) -> PyResult<Option<bool>> {
114 def fallback_symlink(&self) -> PyResult<Option<bool>> {
107 match self.entry(py).get().get_fallback_symlink() {
115 match self.entry(py).get().get_fallback_symlink() {
108 Some(symlink) => Ok(Some(symlink)),
116 Some(symlink) => Ok(Some(symlink)),
109 None => Ok(None),
117 None => Ok(None),
110 }
118 }
111 }
119 }
112
120
113 @fallback_symlink.setter
121 @fallback_symlink.setter
114 def set_fallback_symlink(&self, value: Option<PyObject>) -> PyResult<()> {
122 def set_fallback_symlink(&self, value: Option<PyObject>) -> PyResult<()> {
115 match value {
123 match value {
116 None => {self.entry(py).get().set_fallback_symlink(None);},
124 None => {self.entry(py).get().set_fallback_symlink(None);},
117 Some(value) => {
125 Some(value) => {
118 if value.is_none(py) {
126 if value.is_none(py) {
119 self.entry(py).get().set_fallback_symlink(None);
127 self.entry(py).get().set_fallback_symlink(None);
120 } else {
128 } else {
121 self.entry(py).get().set_fallback_symlink(
129 self.entry(py).get().set_fallback_symlink(
122 Some(value.is_true(py)?)
130 Some(value.is_true(py)?)
123 );
131 );
124 }},
132 }},
125 }
133 }
126 Ok(())
134 Ok(())
127 }
135 }
128
136
129 @property
137 @property
130 def tracked(&self) -> PyResult<bool> {
138 def tracked(&self) -> PyResult<bool> {
131 Ok(self.entry(py).get().tracked())
139 Ok(self.entry(py).get().tracked())
132 }
140 }
133
141
134 @property
142 @property
135 def p1_tracked(&self) -> PyResult<bool> {
143 def p1_tracked(&self) -> PyResult<bool> {
136 Ok(self.entry(py).get().p1_tracked())
144 Ok(self.entry(py).get().p1_tracked())
137 }
145 }
138
146
139 @property
147 @property
140 def added(&self) -> PyResult<bool> {
148 def added(&self) -> PyResult<bool> {
141 Ok(self.entry(py).get().added())
149 Ok(self.entry(py).get().added())
142 }
150 }
143
151
144
152
145 @property
153 @property
146 def p2_info(&self) -> PyResult<bool> {
154 def p2_info(&self) -> PyResult<bool> {
147 Ok(self.entry(py).get().p2_info())
155 Ok(self.entry(py).get().p2_info())
148 }
156 }
149
157
150 @property
158 @property
151 def removed(&self) -> PyResult<bool> {
159 def removed(&self) -> PyResult<bool> {
152 Ok(self.entry(py).get().removed())
160 Ok(self.entry(py).get().removed())
153 }
161 }
154
162
155 @property
163 @property
156 def maybe_clean(&self) -> PyResult<bool> {
164 def maybe_clean(&self) -> PyResult<bool> {
157 Ok(self.entry(py).get().maybe_clean())
165 Ok(self.entry(py).get().maybe_clean())
158 }
166 }
159
167
160 @property
168 @property
161 def any_tracked(&self) -> PyResult<bool> {
169 def any_tracked(&self) -> PyResult<bool> {
162 Ok(self.entry(py).get().any_tracked())
170 Ok(self.entry(py).get().any_tracked())
163 }
171 }
164
172
165 def v1_state(&self) -> PyResult<PyBytes> {
173 def v1_state(&self) -> PyResult<PyBytes> {
166 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
174 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
167 let state_byte: u8 = state.into();
175 let state_byte: u8 = state.into();
168 Ok(PyBytes::new(py, &[state_byte]))
176 Ok(PyBytes::new(py, &[state_byte]))
169 }
177 }
170
178
171 def v1_mode(&self) -> PyResult<i32> {
179 def v1_mode(&self) -> PyResult<i32> {
172 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
180 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
173 Ok(mode)
181 Ok(mode)
174 }
182 }
175
183
176 def v1_size(&self) -> PyResult<i32> {
184 def v1_size(&self) -> PyResult<i32> {
177 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
185 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
178 Ok(size)
186 Ok(size)
179 }
187 }
180
188
181 def v1_mtime(&self) -> PyResult<i32> {
189 def v1_mtime(&self) -> PyResult<i32> {
182 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
190 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
183 Ok(mtime)
191 Ok(mtime)
184 }
192 }
185
193
186 def need_delay(&self, now: i32) -> PyResult<bool> {
194 def need_delay(&self, now: i32) -> PyResult<bool> {
187 Ok(self.entry(py).get().mtime_is_ambiguous(now))
195 Ok(self.entry(py).get().mtime_is_ambiguous(now))
188 }
196 }
189
197
190 @classmethod
198 @classmethod
191 def from_v1_data(
199 def from_v1_data(
192 _cls,
200 _cls,
193 state: PyBytes,
201 state: PyBytes,
194 mode: i32,
202 mode: i32,
195 size: i32,
203 size: i32,
196 mtime: i32,
204 mtime: i32,
197 ) -> PyResult<Self> {
205 ) -> PyResult<Self> {
198 let state = <[u8; 1]>::try_from(state.data(py))
206 let state = <[u8; 1]>::try_from(state.data(py))
199 .ok()
207 .ok()
200 .and_then(|state| EntryState::try_from(state[0]).ok())
208 .and_then(|state| EntryState::try_from(state[0]).ok())
201 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
209 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
202 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
210 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
203 DirstateItem::create_instance(py, Cell::new(entry))
211 DirstateItem::create_instance(py, Cell::new(entry))
204 }
212 }
205
213
206 def drop_merge_data(&self) -> PyResult<PyNone> {
214 def drop_merge_data(&self) -> PyResult<PyNone> {
207 self.update(py, |entry| entry.drop_merge_data());
215 self.update(py, |entry| entry.drop_merge_data());
208 Ok(PyNone)
216 Ok(PyNone)
209 }
217 }
210
218
211 def set_clean(
219 def set_clean(
212 &self,
220 &self,
213 mode: u32,
221 mode: u32,
214 size: u32,
222 size: u32,
215 mtime: u32,
223 mtime: u32,
216 ) -> PyResult<PyNone> {
224 ) -> PyResult<PyNone> {
217 self.update(py, |entry| entry.set_clean(mode, size, mtime));
225 self.update(py, |entry| entry.set_clean(mode, size, mtime));
218 Ok(PyNone)
226 Ok(PyNone)
219 }
227 }
220
228
221 def set_possibly_dirty(&self) -> PyResult<PyNone> {
229 def set_possibly_dirty(&self) -> PyResult<PyNone> {
222 self.update(py, |entry| entry.set_possibly_dirty());
230 self.update(py, |entry| entry.set_possibly_dirty());
223 Ok(PyNone)
231 Ok(PyNone)
224 }
232 }
225
233
226 def set_tracked(&self) -> PyResult<PyNone> {
234 def set_tracked(&self) -> PyResult<PyNone> {
227 self.update(py, |entry| entry.set_tracked());
235 self.update(py, |entry| entry.set_tracked());
228 Ok(PyNone)
236 Ok(PyNone)
229 }
237 }
230
238
231 def set_untracked(&self) -> PyResult<PyNone> {
239 def set_untracked(&self) -> PyResult<PyNone> {
232 self.update(py, |entry| entry.set_untracked());
240 self.update(py, |entry| entry.set_untracked());
233 Ok(PyNone)
241 Ok(PyNone)
234 }
242 }
235 });
243 });
236
244
237 impl DirstateItem {
245 impl DirstateItem {
238 pub fn new_as_pyobject(
246 pub fn new_as_pyobject(
239 py: Python<'_>,
247 py: Python<'_>,
240 entry: DirstateEntry,
248 entry: DirstateEntry,
241 ) -> PyResult<PyObject> {
249 ) -> PyResult<PyObject> {
242 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
250 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
243 }
251 }
244
252
245 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
253 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
246 self.entry(py).get()
254 self.entry(py).get()
247 }
255 }
248
256
249 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
257 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
250 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
258 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
251 let mut entry = self.entry(py).get();
259 let mut entry = self.entry(py).get();
252 f(&mut entry);
260 f(&mut entry);
253 self.entry(py).set(entry)
261 self.entry(py).set(entry)
254 }
262 }
255 }
263 }
General Comments 0
You need to be logged in to leave comments. Login now