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