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