##// END OF EJS Templates
dirstate-v2: initial Python parser...
Simon Sapin -
r49035:a32a9607 default
parent child Browse files
Show More
@@ -0,0 +1,118 b''
1 # v2.py - Pure-Python implementation of the dirstate-v2 file format
2 #
3 # Copyright Mercurial Contributors
4 #
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.
7
8 from __future__ import absolute_import
9
10 import struct
11
12 from .. import policy
13
14 parsers = policy.importmod('parsers')
15
16
17 # Must match the constant of the same name in
18 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
19 TREE_METADATA_SIZE = 44
20 NODE_SIZE = 43
21
22
23 # Must match the `TreeMetadata` Rust struct in
24 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
25 #
26 # * 4 bytes: start offset of root nodes
27 # * 4 bytes: number of root nodes
28 # * 4 bytes: total number of nodes in the tree that have an entry
29 # * 4 bytes: total number of nodes in the tree that have a copy source
30 # * 4 bytes: number of bytes in the data file that are not used anymore
31 # * 4 bytes: unused
32 # * 20 bytes: SHA-1 hash of ignore patterns
33 TREE_METADATA = struct.Struct('>LLLLL4s20s')
34
35
36 # Must match the `Node` Rust struct in
37 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
38 #
39 # * 4 bytes: start offset of full path
40 # * 2 bytes: length of the full path
41 # * 2 bytes: length within the full path before its "base name"
42 # * 4 bytes: start offset of the copy source if any, or zero for no copy source
43 # * 2 bytes: length of the copy source if any, or unused
44 # * 4 bytes: start offset of child nodes
45 # * 4 bytes: number of child nodes
46 # * 4 bytes: number of descendant nodes that have an entry
47 # * 4 bytes: number of descendant nodes that have a "tracked" state
48 # * 1 byte: flags
49 # * 4 bytes: expected size
50 # * 4 bytes: mtime seconds
51 # * 4 bytes: mtime nanoseconds
52 NODE = struct.Struct('>LHHLHLLLLBlll')
53
54
55 assert TREE_METADATA_SIZE == TREE_METADATA.size
56 assert NODE_SIZE == NODE.size
57
58
59 def parse_dirstate(map, copy_map, data, tree_metadata):
60 """parse a full v2-dirstate from a binary data into dictionnaries:
61
62 - map: a {path: entry} mapping that will be filled
63 - copy_map: a {path: copy-source} mapping that will be filled
64 - data: a binary blob contains v2 nodes data
65 - tree_metadata:: a binary blob of the top level node (from the docket)
66 """
67 (
68 root_nodes_start,
69 root_nodes_len,
70 _nodes_with_entry_count,
71 _nodes_with_copy_source_count,
72 _unreachable_bytes,
73 _unused,
74 _ignore_patterns_hash,
75 ) = TREE_METADATA.unpack(tree_metadata)
76 parse_nodes(map, copy_map, data, root_nodes_start, root_nodes_len)
77
78
79 def parse_nodes(map, copy_map, data, start, len):
80 """parse <len> nodes from <data> starting at offset <start>
81
82 This is used by parse_dirstate to recursively fill `map` and `copy_map`.
83 """
84 for i in range(len):
85 node_start = start + NODE_SIZE * i
86 node_bytes = slice_with_len(data, node_start, NODE_SIZE)
87 (
88 path_start,
89 path_len,
90 _basename_start,
91 copy_source_start,
92 copy_source_len,
93 children_start,
94 children_count,
95 _descendants_with_entry_count,
96 _tracked_descendants_count,
97 flags,
98 size,
99 mtime_s,
100 _mtime_ns,
101 ) = NODE.unpack(node_bytes)
102
103 # Parse child nodes of this node recursively
104 parse_nodes(map, copy_map, data, children_start, children_count)
105
106 item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s)
107 if not item.any_tracked:
108 continue
109 path = slice_with_len(data, path_start, path_len)
110 map[path] = item
111 if copy_source_start:
112 copy_map[path] = slice_with_len(
113 data, copy_source_start, copy_source_len
114 )
115
116
117 def slice_with_len(data, start, len):
118 return data[start : start + len]
@@ -1,1126 +1,1155 b''
1 /*
1 /*
2 parsers.c - efficient content parsing
2 parsers.c - efficient content parsing
3
3
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
4 Copyright 2008 Olivia Mackall <olivia@selenic.com> and others
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #define PY_SSIZE_T_CLEAN
10 #define PY_SSIZE_T_CLEAN
11 #include <Python.h>
11 #include <Python.h>
12 #include <ctype.h>
12 #include <ctype.h>
13 #include <stddef.h>
13 #include <stddef.h>
14 #include <string.h>
14 #include <string.h>
15
15
16 #include "bitmanipulation.h"
16 #include "bitmanipulation.h"
17 #include "charencode.h"
17 #include "charencode.h"
18 #include "util.h"
18 #include "util.h"
19
19
20 #ifdef IS_PY3K
20 #ifdef IS_PY3K
21 /* The mapping of Python types is meant to be temporary to get Python
21 /* The mapping of Python types is meant to be temporary to get Python
22 * 3 to compile. We should remove this once Python 3 support is fully
22 * 3 to compile. We should remove this once Python 3 support is fully
23 * supported and proper types are used in the extensions themselves. */
23 * supported and proper types are used in the extensions themselves. */
24 #define PyInt_Check PyLong_Check
24 #define PyInt_Check PyLong_Check
25 #define PyInt_FromLong PyLong_FromLong
25 #define PyInt_FromLong PyLong_FromLong
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
26 #define PyInt_FromSsize_t PyLong_FromSsize_t
27 #define PyInt_AsLong PyLong_AsLong
27 #define PyInt_AsLong PyLong_AsLong
28 #endif
28 #endif
29
29
30 static const char *const versionerrortext = "Python minor version mismatch";
30 static const char *const versionerrortext = "Python minor version mismatch";
31
31
32 static const int dirstate_v1_from_p2 = -2;
32 static const int dirstate_v1_from_p2 = -2;
33 static const int dirstate_v1_nonnormal = -1;
33 static const int dirstate_v1_nonnormal = -1;
34 static const int ambiguous_time = -1;
34 static const int ambiguous_time = -1;
35
35
36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
36 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
37 {
37 {
38 Py_ssize_t expected_size;
38 Py_ssize_t expected_size;
39
39
40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
40 if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size)) {
41 return NULL;
41 return NULL;
42 }
42 }
43
43
44 return _dict_new_presized(expected_size);
44 return _dict_new_presized(expected_size);
45 }
45 }
46
46
47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
47 static PyObject *dirstate_item_new(PyTypeObject *subtype, PyObject *args,
48 PyObject *kwds)
48 PyObject *kwds)
49 {
49 {
50 /* We do all the initialization here and not a tp_init function because
50 /* We do all the initialization here and not a tp_init function because
51 * dirstate_item is immutable. */
51 * dirstate_item is immutable. */
52 dirstateItemObject *t;
52 dirstateItemObject *t;
53 int wc_tracked;
53 int wc_tracked;
54 int p1_tracked;
54 int p1_tracked;
55 int p2_info;
55 int p2_info;
56 int has_meaningful_data;
56 int has_meaningful_data;
57 int has_meaningful_mtime;
57 int has_meaningful_mtime;
58 int mode;
58 int mode;
59 int size;
59 int size;
60 int mtime;
60 int mtime;
61 PyObject *parentfiledata;
61 PyObject *parentfiledata;
62 static char *keywords_name[] = {
62 static char *keywords_name[] = {
63 "wc_tracked",
63 "wc_tracked",
64 "p1_tracked",
64 "p1_tracked",
65 "p2_info",
65 "p2_info",
66 "has_meaningful_data",
66 "has_meaningful_data",
67 "has_meaningful_mtime",
67 "has_meaningful_mtime",
68 "parentfiledata",
68 "parentfiledata",
69 NULL,
69 NULL,
70 };
70 };
71 wc_tracked = 0;
71 wc_tracked = 0;
72 p1_tracked = 0;
72 p1_tracked = 0;
73 p2_info = 0;
73 p2_info = 0;
74 has_meaningful_mtime = 1;
74 has_meaningful_mtime = 1;
75 has_meaningful_data = 1;
75 has_meaningful_data = 1;
76 parentfiledata = Py_None;
76 parentfiledata = Py_None;
77 if (!PyArg_ParseTupleAndKeywords(
77 if (!PyArg_ParseTupleAndKeywords(
78 args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked,
78 args, kwds, "|iiiiiO", keywords_name, &wc_tracked, &p1_tracked,
79 &p2_info, &has_meaningful_data, &has_meaningful_mtime,
79 &p2_info, &has_meaningful_data, &has_meaningful_mtime,
80 &parentfiledata)) {
80 &parentfiledata)) {
81 return NULL;
81 return NULL;
82 }
82 }
83 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
83 t = (dirstateItemObject *)subtype->tp_alloc(subtype, 1);
84 if (!t) {
84 if (!t) {
85 return NULL;
85 return NULL;
86 }
86 }
87
87
88 t->flags = 0;
88 t->flags = 0;
89 if (wc_tracked) {
89 if (wc_tracked) {
90 t->flags |= dirstate_flag_wc_tracked;
90 t->flags |= dirstate_flag_wc_tracked;
91 }
91 }
92 if (p1_tracked) {
92 if (p1_tracked) {
93 t->flags |= dirstate_flag_p1_tracked;
93 t->flags |= dirstate_flag_p1_tracked;
94 }
94 }
95 if (p2_info) {
95 if (p2_info) {
96 t->flags |= dirstate_flag_p2_info;
96 t->flags |= dirstate_flag_p2_info;
97 }
97 }
98
98
99 if (parentfiledata != Py_None) {
99 if (parentfiledata != Py_None) {
100 if (!PyTuple_CheckExact(parentfiledata)) {
100 if (!PyTuple_CheckExact(parentfiledata)) {
101 PyErr_SetString(
101 PyErr_SetString(
102 PyExc_TypeError,
102 PyExc_TypeError,
103 "parentfiledata should be a Tuple or None");
103 "parentfiledata should be a Tuple or None");
104 return NULL;
104 return NULL;
105 }
105 }
106 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
106 mode = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 0));
107 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
107 size = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 1));
108 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
108 mtime = (int)PyLong_AsLong(PyTuple_GetItem(parentfiledata, 2));
109 } else {
109 } else {
110 has_meaningful_data = 0;
110 has_meaningful_data = 0;
111 has_meaningful_mtime = 0;
111 has_meaningful_mtime = 0;
112 }
112 }
113 if (has_meaningful_data) {
113 if (has_meaningful_data) {
114 t->flags |= dirstate_flag_has_meaningful_data;
114 t->flags |= dirstate_flag_has_meaningful_data;
115 t->mode = mode;
115 t->mode = mode;
116 t->size = size;
116 t->size = size;
117 } else {
117 } else {
118 t->mode = 0;
118 t->mode = 0;
119 t->size = 0;
119 t->size = 0;
120 }
120 }
121 if (has_meaningful_mtime) {
121 if (has_meaningful_mtime) {
122 t->flags |= dirstate_flag_has_meaningful_mtime;
122 t->flags |= dirstate_flag_has_meaningful_mtime;
123 t->mtime = mtime;
123 t->mtime = mtime;
124 } else {
124 } else {
125 t->mtime = 0;
125 t->mtime = 0;
126 }
126 }
127 return (PyObject *)t;
127 return (PyObject *)t;
128 }
128 }
129
129
130 static void dirstate_item_dealloc(PyObject *o)
130 static void dirstate_item_dealloc(PyObject *o)
131 {
131 {
132 PyObject_Del(o);
132 PyObject_Del(o);
133 }
133 }
134
134
135 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
135 static inline bool dirstate_item_c_tracked(dirstateItemObject *self)
136 {
136 {
137 return (self->flags & dirstate_flag_wc_tracked);
137 return (self->flags & dirstate_flag_wc_tracked);
138 }
138 }
139
139
140 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
140 static inline bool dirstate_item_c_any_tracked(dirstateItemObject *self)
141 {
141 {
142 const unsigned char mask = dirstate_flag_wc_tracked |
142 const unsigned char mask = dirstate_flag_wc_tracked |
143 dirstate_flag_p1_tracked |
143 dirstate_flag_p1_tracked |
144 dirstate_flag_p2_info;
144 dirstate_flag_p2_info;
145 return (self->flags & mask);
145 return (self->flags & mask);
146 }
146 }
147
147
148 static inline bool dirstate_item_c_added(dirstateItemObject *self)
148 static inline bool dirstate_item_c_added(dirstateItemObject *self)
149 {
149 {
150 const unsigned char mask =
150 const unsigned char mask =
151 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
151 (dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
152 dirstate_flag_p2_info);
152 dirstate_flag_p2_info);
153 const unsigned char target = dirstate_flag_wc_tracked;
153 const unsigned char target = dirstate_flag_wc_tracked;
154 return (self->flags & mask) == target;
154 return (self->flags & mask) == target;
155 }
155 }
156
156
157 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
157 static inline bool dirstate_item_c_removed(dirstateItemObject *self)
158 {
158 {
159 if (self->flags & dirstate_flag_wc_tracked) {
159 if (self->flags & dirstate_flag_wc_tracked) {
160 return false;
160 return false;
161 }
161 }
162 return (self->flags &
162 return (self->flags &
163 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
163 (dirstate_flag_p1_tracked | dirstate_flag_p2_info));
164 }
164 }
165
165
166 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
166 static inline bool dirstate_item_c_merged(dirstateItemObject *self)
167 {
167 {
168 return ((self->flags & dirstate_flag_wc_tracked) &&
168 return ((self->flags & dirstate_flag_wc_tracked) &&
169 (self->flags & dirstate_flag_p1_tracked) &&
169 (self->flags & dirstate_flag_p1_tracked) &&
170 (self->flags & dirstate_flag_p2_info));
170 (self->flags & dirstate_flag_p2_info));
171 }
171 }
172
172
173 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
173 static inline bool dirstate_item_c_from_p2(dirstateItemObject *self)
174 {
174 {
175 return ((self->flags & dirstate_flag_wc_tracked) &&
175 return ((self->flags & dirstate_flag_wc_tracked) &&
176 !(self->flags & dirstate_flag_p1_tracked) &&
176 !(self->flags & dirstate_flag_p1_tracked) &&
177 (self->flags & dirstate_flag_p2_info));
177 (self->flags & dirstate_flag_p2_info));
178 }
178 }
179
179
180 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
180 static inline char dirstate_item_c_v1_state(dirstateItemObject *self)
181 {
181 {
182 if (dirstate_item_c_removed(self)) {
182 if (dirstate_item_c_removed(self)) {
183 return 'r';
183 return 'r';
184 } else if (dirstate_item_c_merged(self)) {
184 } else if (dirstate_item_c_merged(self)) {
185 return 'm';
185 return 'm';
186 } else if (dirstate_item_c_added(self)) {
186 } else if (dirstate_item_c_added(self)) {
187 return 'a';
187 return 'a';
188 } else {
188 } else {
189 return 'n';
189 return 'n';
190 }
190 }
191 }
191 }
192
192
193 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
193 static inline int dirstate_item_c_v1_mode(dirstateItemObject *self)
194 {
194 {
195 if (self->flags & dirstate_flag_has_meaningful_data) {
195 if (self->flags & dirstate_flag_has_meaningful_data) {
196 return self->mode;
196 return self->mode;
197 } else {
197 } else {
198 return 0;
198 return 0;
199 }
199 }
200 }
200 }
201
201
202 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
202 static inline int dirstate_item_c_v1_size(dirstateItemObject *self)
203 {
203 {
204 if (!(self->flags & dirstate_flag_wc_tracked) &&
204 if (!(self->flags & dirstate_flag_wc_tracked) &&
205 (self->flags & dirstate_flag_p2_info)) {
205 (self->flags & dirstate_flag_p2_info)) {
206 if (self->flags & dirstate_flag_p1_tracked) {
206 if (self->flags & dirstate_flag_p1_tracked) {
207 return dirstate_v1_nonnormal;
207 return dirstate_v1_nonnormal;
208 } else {
208 } else {
209 return dirstate_v1_from_p2;
209 return dirstate_v1_from_p2;
210 }
210 }
211 } else if (dirstate_item_c_removed(self)) {
211 } else if (dirstate_item_c_removed(self)) {
212 return 0;
212 return 0;
213 } else if (self->flags & dirstate_flag_p2_info) {
213 } else if (self->flags & dirstate_flag_p2_info) {
214 return dirstate_v1_from_p2;
214 return dirstate_v1_from_p2;
215 } else if (dirstate_item_c_added(self)) {
215 } else if (dirstate_item_c_added(self)) {
216 return dirstate_v1_nonnormal;
216 return dirstate_v1_nonnormal;
217 } else if (self->flags & dirstate_flag_has_meaningful_data) {
217 } else if (self->flags & dirstate_flag_has_meaningful_data) {
218 return self->size;
218 return self->size;
219 } else {
219 } else {
220 return dirstate_v1_nonnormal;
220 return dirstate_v1_nonnormal;
221 }
221 }
222 }
222 }
223
223
224 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
224 static inline int dirstate_item_c_v1_mtime(dirstateItemObject *self)
225 {
225 {
226 if (dirstate_item_c_removed(self)) {
226 if (dirstate_item_c_removed(self)) {
227 return 0;
227 return 0;
228 } else if (!(self->flags & dirstate_flag_has_meaningful_mtime) ||
228 } else if (!(self->flags & dirstate_flag_has_meaningful_mtime) ||
229 !(self->flags & dirstate_flag_p1_tracked) ||
229 !(self->flags & dirstate_flag_p1_tracked) ||
230 !(self->flags & dirstate_flag_wc_tracked) ||
230 !(self->flags & dirstate_flag_wc_tracked) ||
231 (self->flags & dirstate_flag_p2_info)) {
231 (self->flags & dirstate_flag_p2_info)) {
232 return ambiguous_time;
232 return ambiguous_time;
233 } else {
233 } else {
234 return self->mtime;
234 return self->mtime;
235 }
235 }
236 }
236 }
237
237
238 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
238 static PyObject *dirstate_item_v1_state(dirstateItemObject *self)
239 {
239 {
240 char state = dirstate_item_c_v1_state(self);
240 char state = dirstate_item_c_v1_state(self);
241 return PyBytes_FromStringAndSize(&state, 1);
241 return PyBytes_FromStringAndSize(&state, 1);
242 };
242 };
243
243
244 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
244 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
245 {
245 {
246 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
246 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
247 };
247 };
248
248
249 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
249 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
250 {
250 {
251 return PyInt_FromLong(dirstate_item_c_v1_size(self));
251 return PyInt_FromLong(dirstate_item_c_v1_size(self));
252 };
252 };
253
253
254 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
254 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
255 {
255 {
256 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
256 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
257 };
257 };
258
258
259 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
259 static PyObject *dirstate_item_need_delay(dirstateItemObject *self,
260 PyObject *value)
260 PyObject *value)
261 {
261 {
262 long now;
262 long now;
263 if (!pylong_to_long(value, &now)) {
263 if (!pylong_to_long(value, &now)) {
264 return NULL;
264 return NULL;
265 }
265 }
266 if (dirstate_item_c_v1_state(self) == 'n' &&
266 if (dirstate_item_c_v1_state(self) == 'n' &&
267 dirstate_item_c_v1_mtime(self) == now) {
267 dirstate_item_c_v1_mtime(self) == now) {
268 Py_RETURN_TRUE;
268 Py_RETURN_TRUE;
269 } else {
269 } else {
270 Py_RETURN_FALSE;
270 Py_RETURN_FALSE;
271 }
271 }
272 };
272 };
273
273
274 /* This will never change since it's bound to V1
274 /* This will never change since it's bound to V1
275 */
275 */
276 static inline dirstateItemObject *
276 static inline dirstateItemObject *
277 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
277 dirstate_item_from_v1_data(char state, int mode, int size, int mtime)
278 {
278 {
279 dirstateItemObject *t =
279 dirstateItemObject *t =
280 PyObject_New(dirstateItemObject, &dirstateItemType);
280 PyObject_New(dirstateItemObject, &dirstateItemType);
281 if (!t) {
281 if (!t) {
282 return NULL;
282 return NULL;
283 }
283 }
284 t->flags = 0;
284 t->flags = 0;
285 t->mode = 0;
285 t->mode = 0;
286 t->size = 0;
286 t->size = 0;
287 t->mtime = 0;
287 t->mtime = 0;
288
288
289 if (state == 'm') {
289 if (state == 'm') {
290 t->flags = (dirstate_flag_wc_tracked |
290 t->flags = (dirstate_flag_wc_tracked |
291 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
291 dirstate_flag_p1_tracked | dirstate_flag_p2_info);
292 } else if (state == 'a') {
292 } else if (state == 'a') {
293 t->flags = dirstate_flag_wc_tracked;
293 t->flags = dirstate_flag_wc_tracked;
294 } else if (state == 'r') {
294 } else if (state == 'r') {
295 if (size == dirstate_v1_nonnormal) {
295 if (size == dirstate_v1_nonnormal) {
296 t->flags =
296 t->flags =
297 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
297 dirstate_flag_p1_tracked | dirstate_flag_p2_info;
298 } else if (size == dirstate_v1_from_p2) {
298 } else if (size == dirstate_v1_from_p2) {
299 t->flags = dirstate_flag_p2_info;
299 t->flags = dirstate_flag_p2_info;
300 } else {
300 } else {
301 t->flags = dirstate_flag_p1_tracked;
301 t->flags = dirstate_flag_p1_tracked;
302 }
302 }
303 } else if (state == 'n') {
303 } else if (state == 'n') {
304 if (size == dirstate_v1_from_p2) {
304 if (size == dirstate_v1_from_p2) {
305 t->flags =
305 t->flags =
306 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
306 dirstate_flag_wc_tracked | dirstate_flag_p2_info;
307 } else if (size == dirstate_v1_nonnormal) {
307 } else if (size == dirstate_v1_nonnormal) {
308 t->flags =
308 t->flags =
309 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
309 dirstate_flag_wc_tracked | dirstate_flag_p1_tracked;
310 } else if (mtime == ambiguous_time) {
310 } else if (mtime == ambiguous_time) {
311 t->flags = (dirstate_flag_wc_tracked |
311 t->flags = (dirstate_flag_wc_tracked |
312 dirstate_flag_p1_tracked |
312 dirstate_flag_p1_tracked |
313 dirstate_flag_has_meaningful_data);
313 dirstate_flag_has_meaningful_data);
314 t->mode = mode;
314 t->mode = mode;
315 t->size = size;
315 t->size = size;
316 } else {
316 } else {
317 t->flags = (dirstate_flag_wc_tracked |
317 t->flags = (dirstate_flag_wc_tracked |
318 dirstate_flag_p1_tracked |
318 dirstate_flag_p1_tracked |
319 dirstate_flag_has_meaningful_data |
319 dirstate_flag_has_meaningful_data |
320 dirstate_flag_has_meaningful_mtime);
320 dirstate_flag_has_meaningful_mtime);
321 t->mode = mode;
321 t->mode = mode;
322 t->size = size;
322 t->size = size;
323 t->mtime = mtime;
323 t->mtime = mtime;
324 }
324 }
325 } else {
325 } else {
326 PyErr_Format(PyExc_RuntimeError,
326 PyErr_Format(PyExc_RuntimeError,
327 "unknown state: `%c` (%d, %d, %d)", state, mode,
327 "unknown state: `%c` (%d, %d, %d)", state, mode,
328 size, mtime, NULL);
328 size, mtime, NULL);
329 Py_DECREF(t);
329 Py_DECREF(t);
330 return NULL;
330 return NULL;
331 }
331 }
332
332
333 return t;
333 return t;
334 }
334 }
335
335
336 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
336 /* This will never change since it's bound to V1, unlike `dirstate_item_new` */
337 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
337 static PyObject *dirstate_item_from_v1_meth(PyTypeObject *subtype,
338 PyObject *args)
338 PyObject *args)
339 {
339 {
340 /* We do all the initialization here and not a tp_init function because
340 /* We do all the initialization here and not a tp_init function because
341 * dirstate_item is immutable. */
341 * dirstate_item is immutable. */
342 char state;
342 char state;
343 int size, mode, mtime;
343 int size, mode, mtime;
344 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
344 if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime)) {
345 return NULL;
345 return NULL;
346 }
346 }
347 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
347 return (PyObject *)dirstate_item_from_v1_data(state, mode, size, mtime);
348 };
348 };
349
349
350 static PyObject *dirstate_item_from_v2_meth(PyTypeObject *subtype,
351 PyObject *args)
352 {
353 dirstateItemObject *t =
354 PyObject_New(dirstateItemObject, &dirstateItemType);
355 if (!t) {
356 return NULL;
357 }
358 if (!PyArg_ParseTuple(args, "bii", &t->flags, &t->size, &t->mtime)) {
359 return NULL;
360 }
361 t->mode = 0;
362 if (t->flags & dirstate_flag_has_meaningful_data) {
363 if (t->flags & dirstate_flag_mode_exec_perm) {
364 t->mode = 0755;
365 } else {
366 t->mode = 0644;
367 }
368 if (t->flags & dirstate_flag_mode_is_symlink) {
369 t->mode |= S_IFLNK;
370 } else {
371 t->mode |= S_IFREG;
372 }
373 }
374 return (PyObject *)t;
375 };
376
350 /* This means the next status call will have to actually check its content
377 /* This means the next status call will have to actually check its content
351 to make sure it is correct. */
378 to make sure it is correct. */
352 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
379 static PyObject *dirstate_item_set_possibly_dirty(dirstateItemObject *self)
353 {
380 {
354 self->flags &= ~dirstate_flag_has_meaningful_mtime;
381 self->flags &= ~dirstate_flag_has_meaningful_mtime;
355 Py_RETURN_NONE;
382 Py_RETURN_NONE;
356 }
383 }
357
384
358 /* See docstring of the python implementation for details */
385 /* See docstring of the python implementation for details */
359 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
386 static PyObject *dirstate_item_set_clean(dirstateItemObject *self,
360 PyObject *args)
387 PyObject *args)
361 {
388 {
362 int size, mode, mtime;
389 int size, mode, mtime;
363 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
390 if (!PyArg_ParseTuple(args, "iii", &mode, &size, &mtime)) {
364 return NULL;
391 return NULL;
365 }
392 }
366 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
393 self->flags = dirstate_flag_wc_tracked | dirstate_flag_p1_tracked |
367 dirstate_flag_has_meaningful_data |
394 dirstate_flag_has_meaningful_data |
368 dirstate_flag_has_meaningful_mtime;
395 dirstate_flag_has_meaningful_mtime;
369 self->mode = mode;
396 self->mode = mode;
370 self->size = size;
397 self->size = size;
371 self->mtime = mtime;
398 self->mtime = mtime;
372 Py_RETURN_NONE;
399 Py_RETURN_NONE;
373 }
400 }
374
401
375 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
402 static PyObject *dirstate_item_set_tracked(dirstateItemObject *self)
376 {
403 {
377 self->flags |= dirstate_flag_wc_tracked;
404 self->flags |= dirstate_flag_wc_tracked;
378 self->flags &= ~dirstate_flag_has_meaningful_mtime;
405 self->flags &= ~dirstate_flag_has_meaningful_mtime;
379 Py_RETURN_NONE;
406 Py_RETURN_NONE;
380 }
407 }
381
408
382 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
409 static PyObject *dirstate_item_set_untracked(dirstateItemObject *self)
383 {
410 {
384 self->flags &= ~dirstate_flag_wc_tracked;
411 self->flags &= ~dirstate_flag_wc_tracked;
385 self->mode = 0;
412 self->mode = 0;
386 self->mtime = 0;
413 self->mtime = 0;
387 self->size = 0;
414 self->size = 0;
388 Py_RETURN_NONE;
415 Py_RETURN_NONE;
389 }
416 }
390
417
391 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
418 static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self)
392 {
419 {
393 if (self->flags & dirstate_flag_p2_info) {
420 if (self->flags & dirstate_flag_p2_info) {
394 self->flags &= ~(dirstate_flag_p2_info |
421 self->flags &= ~(dirstate_flag_p2_info |
395 dirstate_flag_has_meaningful_data |
422 dirstate_flag_has_meaningful_data |
396 dirstate_flag_has_meaningful_mtime);
423 dirstate_flag_has_meaningful_mtime);
397 self->mode = 0;
424 self->mode = 0;
398 self->mtime = 0;
425 self->mtime = 0;
399 self->size = 0;
426 self->size = 0;
400 }
427 }
401 Py_RETURN_NONE;
428 Py_RETURN_NONE;
402 }
429 }
403 static PyMethodDef dirstate_item_methods[] = {
430 static PyMethodDef dirstate_item_methods[] = {
404 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
431 {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS,
405 "return a \"state\" suitable for v1 serialization"},
432 "return a \"state\" suitable for v1 serialization"},
406 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
433 {"v1_mode", (PyCFunction)dirstate_item_v1_mode, METH_NOARGS,
407 "return a \"mode\" suitable for v1 serialization"},
434 "return a \"mode\" suitable for v1 serialization"},
408 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
435 {"v1_size", (PyCFunction)dirstate_item_v1_size, METH_NOARGS,
409 "return a \"size\" suitable for v1 serialization"},
436 "return a \"size\" suitable for v1 serialization"},
410 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
437 {"v1_mtime", (PyCFunction)dirstate_item_v1_mtime, METH_NOARGS,
411 "return a \"mtime\" suitable for v1 serialization"},
438 "return a \"mtime\" suitable for v1 serialization"},
412 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
439 {"need_delay", (PyCFunction)dirstate_item_need_delay, METH_O,
413 "True if the stored mtime would be ambiguous with the current time"},
440 "True if the stored mtime would be ambiguous with the current time"},
414 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
441 {"from_v1_data", (PyCFunction)dirstate_item_from_v1_meth,
415 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
442 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V1 data"},
443 {"from_v2_data", (PyCFunction)dirstate_item_from_v2_meth,
444 METH_VARARGS | METH_CLASS, "build a new DirstateItem object from V2 data"},
416 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
445 {"set_possibly_dirty", (PyCFunction)dirstate_item_set_possibly_dirty,
417 METH_NOARGS, "mark a file as \"possibly dirty\""},
446 METH_NOARGS, "mark a file as \"possibly dirty\""},
418 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
447 {"set_clean", (PyCFunction)dirstate_item_set_clean, METH_VARARGS,
419 "mark a file as \"clean\""},
448 "mark a file as \"clean\""},
420 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
449 {"set_tracked", (PyCFunction)dirstate_item_set_tracked, METH_NOARGS,
421 "mark a file as \"tracked\""},
450 "mark a file as \"tracked\""},
422 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
451 {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS,
423 "mark a file as \"untracked\""},
452 "mark a file as \"untracked\""},
424 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
453 {"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS,
425 "remove all \"merge-only\" from a DirstateItem"},
454 "remove all \"merge-only\" from a DirstateItem"},
426 {NULL} /* Sentinel */
455 {NULL} /* Sentinel */
427 };
456 };
428
457
429 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
458 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
430 {
459 {
431 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
460 return PyInt_FromLong(dirstate_item_c_v1_mode(self));
432 };
461 };
433
462
434 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
463 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
435 {
464 {
436 return PyInt_FromLong(dirstate_item_c_v1_size(self));
465 return PyInt_FromLong(dirstate_item_c_v1_size(self));
437 };
466 };
438
467
439 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
468 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
440 {
469 {
441 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
470 return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
442 };
471 };
443
472
444 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
473 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
445 {
474 {
446 char state = dirstate_item_c_v1_state(self);
475 char state = dirstate_item_c_v1_state(self);
447 return PyBytes_FromStringAndSize(&state, 1);
476 return PyBytes_FromStringAndSize(&state, 1);
448 };
477 };
449
478
450 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
479 static PyObject *dirstate_item_get_tracked(dirstateItemObject *self)
451 {
480 {
452 if (dirstate_item_c_tracked(self)) {
481 if (dirstate_item_c_tracked(self)) {
453 Py_RETURN_TRUE;
482 Py_RETURN_TRUE;
454 } else {
483 } else {
455 Py_RETURN_FALSE;
484 Py_RETURN_FALSE;
456 }
485 }
457 };
486 };
458 static PyObject *dirstate_item_get_p1_tracked(dirstateItemObject *self)
487 static PyObject *dirstate_item_get_p1_tracked(dirstateItemObject *self)
459 {
488 {
460 if (self->flags & dirstate_flag_p1_tracked) {
489 if (self->flags & dirstate_flag_p1_tracked) {
461 Py_RETURN_TRUE;
490 Py_RETURN_TRUE;
462 } else {
491 } else {
463 Py_RETURN_FALSE;
492 Py_RETURN_FALSE;
464 }
493 }
465 };
494 };
466
495
467 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
496 static PyObject *dirstate_item_get_added(dirstateItemObject *self)
468 {
497 {
469 if (dirstate_item_c_added(self)) {
498 if (dirstate_item_c_added(self)) {
470 Py_RETURN_TRUE;
499 Py_RETURN_TRUE;
471 } else {
500 } else {
472 Py_RETURN_FALSE;
501 Py_RETURN_FALSE;
473 }
502 }
474 };
503 };
475
504
476 static PyObject *dirstate_item_get_p2_info(dirstateItemObject *self)
505 static PyObject *dirstate_item_get_p2_info(dirstateItemObject *self)
477 {
506 {
478 if (self->flags & dirstate_flag_wc_tracked &&
507 if (self->flags & dirstate_flag_wc_tracked &&
479 self->flags & dirstate_flag_p2_info) {
508 self->flags & dirstate_flag_p2_info) {
480 Py_RETURN_TRUE;
509 Py_RETURN_TRUE;
481 } else {
510 } else {
482 Py_RETURN_FALSE;
511 Py_RETURN_FALSE;
483 }
512 }
484 };
513 };
485
514
486 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
515 static PyObject *dirstate_item_get_merged(dirstateItemObject *self)
487 {
516 {
488 if (dirstate_item_c_merged(self)) {
517 if (dirstate_item_c_merged(self)) {
489 Py_RETURN_TRUE;
518 Py_RETURN_TRUE;
490 } else {
519 } else {
491 Py_RETURN_FALSE;
520 Py_RETURN_FALSE;
492 }
521 }
493 };
522 };
494
523
495 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
524 static PyObject *dirstate_item_get_from_p2(dirstateItemObject *self)
496 {
525 {
497 if (dirstate_item_c_from_p2(self)) {
526 if (dirstate_item_c_from_p2(self)) {
498 Py_RETURN_TRUE;
527 Py_RETURN_TRUE;
499 } else {
528 } else {
500 Py_RETURN_FALSE;
529 Py_RETURN_FALSE;
501 }
530 }
502 };
531 };
503
532
504 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
533 static PyObject *dirstate_item_get_maybe_clean(dirstateItemObject *self)
505 {
534 {
506 if (!(self->flags & dirstate_flag_wc_tracked)) {
535 if (!(self->flags & dirstate_flag_wc_tracked)) {
507 Py_RETURN_FALSE;
536 Py_RETURN_FALSE;
508 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
537 } else if (!(self->flags & dirstate_flag_p1_tracked)) {
509 Py_RETURN_FALSE;
538 Py_RETURN_FALSE;
510 } else if (self->flags & dirstate_flag_p2_info) {
539 } else if (self->flags & dirstate_flag_p2_info) {
511 Py_RETURN_FALSE;
540 Py_RETURN_FALSE;
512 } else {
541 } else {
513 Py_RETURN_TRUE;
542 Py_RETURN_TRUE;
514 }
543 }
515 };
544 };
516
545
517 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
546 static PyObject *dirstate_item_get_any_tracked(dirstateItemObject *self)
518 {
547 {
519 if (dirstate_item_c_any_tracked(self)) {
548 if (dirstate_item_c_any_tracked(self)) {
520 Py_RETURN_TRUE;
549 Py_RETURN_TRUE;
521 } else {
550 } else {
522 Py_RETURN_FALSE;
551 Py_RETURN_FALSE;
523 }
552 }
524 };
553 };
525
554
526 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
555 static PyObject *dirstate_item_get_removed(dirstateItemObject *self)
527 {
556 {
528 if (dirstate_item_c_removed(self)) {
557 if (dirstate_item_c_removed(self)) {
529 Py_RETURN_TRUE;
558 Py_RETURN_TRUE;
530 } else {
559 } else {
531 Py_RETURN_FALSE;
560 Py_RETURN_FALSE;
532 }
561 }
533 };
562 };
534
563
535 static PyGetSetDef dirstate_item_getset[] = {
564 static PyGetSetDef dirstate_item_getset[] = {
536 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
565 {"mode", (getter)dirstate_item_get_mode, NULL, "mode", NULL},
537 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
566 {"size", (getter)dirstate_item_get_size, NULL, "size", NULL},
538 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
567 {"mtime", (getter)dirstate_item_get_mtime, NULL, "mtime", NULL},
539 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
568 {"state", (getter)dirstate_item_get_state, NULL, "state", NULL},
540 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
569 {"tracked", (getter)dirstate_item_get_tracked, NULL, "tracked", NULL},
541 {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
570 {"p1_tracked", (getter)dirstate_item_get_p1_tracked, NULL, "p1_tracked",
542 NULL},
571 NULL},
543 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
572 {"added", (getter)dirstate_item_get_added, NULL, "added", NULL},
544 {"p2_info", (getter)dirstate_item_get_p2_info, NULL, "p2_info", NULL},
573 {"p2_info", (getter)dirstate_item_get_p2_info, NULL, "p2_info", NULL},
545 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
574 {"merged", (getter)dirstate_item_get_merged, NULL, "merged", NULL},
546 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
575 {"from_p2", (getter)dirstate_item_get_from_p2, NULL, "from_p2", NULL},
547 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
576 {"maybe_clean", (getter)dirstate_item_get_maybe_clean, NULL, "maybe_clean",
548 NULL},
577 NULL},
549 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
578 {"any_tracked", (getter)dirstate_item_get_any_tracked, NULL, "any_tracked",
550 NULL},
579 NULL},
551 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
580 {"removed", (getter)dirstate_item_get_removed, NULL, "removed", NULL},
552 {NULL} /* Sentinel */
581 {NULL} /* Sentinel */
553 };
582 };
554
583
555 PyTypeObject dirstateItemType = {
584 PyTypeObject dirstateItemType = {
556 PyVarObject_HEAD_INIT(NULL, 0) /* header */
585 PyVarObject_HEAD_INIT(NULL, 0) /* header */
557 "dirstate_tuple", /* tp_name */
586 "dirstate_tuple", /* tp_name */
558 sizeof(dirstateItemObject), /* tp_basicsize */
587 sizeof(dirstateItemObject), /* tp_basicsize */
559 0, /* tp_itemsize */
588 0, /* tp_itemsize */
560 (destructor)dirstate_item_dealloc, /* tp_dealloc */
589 (destructor)dirstate_item_dealloc, /* tp_dealloc */
561 0, /* tp_print */
590 0, /* tp_print */
562 0, /* tp_getattr */
591 0, /* tp_getattr */
563 0, /* tp_setattr */
592 0, /* tp_setattr */
564 0, /* tp_compare */
593 0, /* tp_compare */
565 0, /* tp_repr */
594 0, /* tp_repr */
566 0, /* tp_as_number */
595 0, /* tp_as_number */
567 0, /* tp_as_sequence */
596 0, /* tp_as_sequence */
568 0, /* tp_as_mapping */
597 0, /* tp_as_mapping */
569 0, /* tp_hash */
598 0, /* tp_hash */
570 0, /* tp_call */
599 0, /* tp_call */
571 0, /* tp_str */
600 0, /* tp_str */
572 0, /* tp_getattro */
601 0, /* tp_getattro */
573 0, /* tp_setattro */
602 0, /* tp_setattro */
574 0, /* tp_as_buffer */
603 0, /* tp_as_buffer */
575 Py_TPFLAGS_DEFAULT, /* tp_flags */
604 Py_TPFLAGS_DEFAULT, /* tp_flags */
576 "dirstate tuple", /* tp_doc */
605 "dirstate tuple", /* tp_doc */
577 0, /* tp_traverse */
606 0, /* tp_traverse */
578 0, /* tp_clear */
607 0, /* tp_clear */
579 0, /* tp_richcompare */
608 0, /* tp_richcompare */
580 0, /* tp_weaklistoffset */
609 0, /* tp_weaklistoffset */
581 0, /* tp_iter */
610 0, /* tp_iter */
582 0, /* tp_iternext */
611 0, /* tp_iternext */
583 dirstate_item_methods, /* tp_methods */
612 dirstate_item_methods, /* tp_methods */
584 0, /* tp_members */
613 0, /* tp_members */
585 dirstate_item_getset, /* tp_getset */
614 dirstate_item_getset, /* tp_getset */
586 0, /* tp_base */
615 0, /* tp_base */
587 0, /* tp_dict */
616 0, /* tp_dict */
588 0, /* tp_descr_get */
617 0, /* tp_descr_get */
589 0, /* tp_descr_set */
618 0, /* tp_descr_set */
590 0, /* tp_dictoffset */
619 0, /* tp_dictoffset */
591 0, /* tp_init */
620 0, /* tp_init */
592 0, /* tp_alloc */
621 0, /* tp_alloc */
593 dirstate_item_new, /* tp_new */
622 dirstate_item_new, /* tp_new */
594 };
623 };
595
624
596 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
625 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
597 {
626 {
598 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
627 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
599 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
628 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
600 char state, *cur, *str, *cpos;
629 char state, *cur, *str, *cpos;
601 int mode, size, mtime;
630 int mode, size, mtime;
602 unsigned int flen, pos = 40;
631 unsigned int flen, pos = 40;
603 Py_ssize_t len = 40;
632 Py_ssize_t len = 40;
604 Py_ssize_t readlen;
633 Py_ssize_t readlen;
605
634
606 if (!PyArg_ParseTuple(
635 if (!PyArg_ParseTuple(
607 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
636 args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
608 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
637 &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
609 goto quit;
638 goto quit;
610 }
639 }
611
640
612 len = readlen;
641 len = readlen;
613
642
614 /* read parents */
643 /* read parents */
615 if (len < 40) {
644 if (len < 40) {
616 PyErr_SetString(PyExc_ValueError,
645 PyErr_SetString(PyExc_ValueError,
617 "too little data for parents");
646 "too little data for parents");
618 goto quit;
647 goto quit;
619 }
648 }
620
649
621 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
650 parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
622 str + 20, (Py_ssize_t)20);
651 str + 20, (Py_ssize_t)20);
623 if (!parents) {
652 if (!parents) {
624 goto quit;
653 goto quit;
625 }
654 }
626
655
627 /* read filenames */
656 /* read filenames */
628 while (pos >= 40 && pos < len) {
657 while (pos >= 40 && pos < len) {
629 if (pos + 17 > len) {
658 if (pos + 17 > len) {
630 PyErr_SetString(PyExc_ValueError,
659 PyErr_SetString(PyExc_ValueError,
631 "overflow in dirstate");
660 "overflow in dirstate");
632 goto quit;
661 goto quit;
633 }
662 }
634 cur = str + pos;
663 cur = str + pos;
635 /* unpack header */
664 /* unpack header */
636 state = *cur;
665 state = *cur;
637 mode = getbe32(cur + 1);
666 mode = getbe32(cur + 1);
638 size = getbe32(cur + 5);
667 size = getbe32(cur + 5);
639 mtime = getbe32(cur + 9);
668 mtime = getbe32(cur + 9);
640 flen = getbe32(cur + 13);
669 flen = getbe32(cur + 13);
641 pos += 17;
670 pos += 17;
642 cur += 17;
671 cur += 17;
643 if (flen > len - pos) {
672 if (flen > len - pos) {
644 PyErr_SetString(PyExc_ValueError,
673 PyErr_SetString(PyExc_ValueError,
645 "overflow in dirstate");
674 "overflow in dirstate");
646 goto quit;
675 goto quit;
647 }
676 }
648
677
649 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
678 entry = (PyObject *)dirstate_item_from_v1_data(state, mode,
650 size, mtime);
679 size, mtime);
651 if (!entry)
680 if (!entry)
652 goto quit;
681 goto quit;
653 cpos = memchr(cur, 0, flen);
682 cpos = memchr(cur, 0, flen);
654 if (cpos) {
683 if (cpos) {
655 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
684 fname = PyBytes_FromStringAndSize(cur, cpos - cur);
656 cname = PyBytes_FromStringAndSize(
685 cname = PyBytes_FromStringAndSize(
657 cpos + 1, flen - (cpos - cur) - 1);
686 cpos + 1, flen - (cpos - cur) - 1);
658 if (!fname || !cname ||
687 if (!fname || !cname ||
659 PyDict_SetItem(cmap, fname, cname) == -1 ||
688 PyDict_SetItem(cmap, fname, cname) == -1 ||
660 PyDict_SetItem(dmap, fname, entry) == -1) {
689 PyDict_SetItem(dmap, fname, entry) == -1) {
661 goto quit;
690 goto quit;
662 }
691 }
663 Py_DECREF(cname);
692 Py_DECREF(cname);
664 } else {
693 } else {
665 fname = PyBytes_FromStringAndSize(cur, flen);
694 fname = PyBytes_FromStringAndSize(cur, flen);
666 if (!fname ||
695 if (!fname ||
667 PyDict_SetItem(dmap, fname, entry) == -1) {
696 PyDict_SetItem(dmap, fname, entry) == -1) {
668 goto quit;
697 goto quit;
669 }
698 }
670 }
699 }
671 Py_DECREF(fname);
700 Py_DECREF(fname);
672 Py_DECREF(entry);
701 Py_DECREF(entry);
673 fname = cname = entry = NULL;
702 fname = cname = entry = NULL;
674 pos += flen;
703 pos += flen;
675 }
704 }
676
705
677 ret = parents;
706 ret = parents;
678 Py_INCREF(ret);
707 Py_INCREF(ret);
679 quit:
708 quit:
680 Py_XDECREF(fname);
709 Py_XDECREF(fname);
681 Py_XDECREF(cname);
710 Py_XDECREF(cname);
682 Py_XDECREF(entry);
711 Py_XDECREF(entry);
683 Py_XDECREF(parents);
712 Py_XDECREF(parents);
684 return ret;
713 return ret;
685 }
714 }
686
715
687 /*
716 /*
688 * Efficiently pack a dirstate object into its on-disk format.
717 * Efficiently pack a dirstate object into its on-disk format.
689 */
718 */
690 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
719 static PyObject *pack_dirstate(PyObject *self, PyObject *args)
691 {
720 {
692 PyObject *packobj = NULL;
721 PyObject *packobj = NULL;
693 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
722 PyObject *map, *copymap, *pl, *mtime_unset = NULL;
694 Py_ssize_t nbytes, pos, l;
723 Py_ssize_t nbytes, pos, l;
695 PyObject *k, *v = NULL, *pn;
724 PyObject *k, *v = NULL, *pn;
696 char *p, *s;
725 char *p, *s;
697 int now;
726 int now;
698
727
699 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
728 if (!PyArg_ParseTuple(args, "O!O!O!i:pack_dirstate", &PyDict_Type, &map,
700 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
729 &PyDict_Type, &copymap, &PyTuple_Type, &pl,
701 &now)) {
730 &now)) {
702 return NULL;
731 return NULL;
703 }
732 }
704
733
705 if (PyTuple_Size(pl) != 2) {
734 if (PyTuple_Size(pl) != 2) {
706 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
735 PyErr_SetString(PyExc_TypeError, "expected 2-element tuple");
707 return NULL;
736 return NULL;
708 }
737 }
709
738
710 /* Figure out how much we need to allocate. */
739 /* Figure out how much we need to allocate. */
711 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
740 for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
712 PyObject *c;
741 PyObject *c;
713 if (!PyBytes_Check(k)) {
742 if (!PyBytes_Check(k)) {
714 PyErr_SetString(PyExc_TypeError, "expected string key");
743 PyErr_SetString(PyExc_TypeError, "expected string key");
715 goto bail;
744 goto bail;
716 }
745 }
717 nbytes += PyBytes_GET_SIZE(k) + 17;
746 nbytes += PyBytes_GET_SIZE(k) + 17;
718 c = PyDict_GetItem(copymap, k);
747 c = PyDict_GetItem(copymap, k);
719 if (c) {
748 if (c) {
720 if (!PyBytes_Check(c)) {
749 if (!PyBytes_Check(c)) {
721 PyErr_SetString(PyExc_TypeError,
750 PyErr_SetString(PyExc_TypeError,
722 "expected string key");
751 "expected string key");
723 goto bail;
752 goto bail;
724 }
753 }
725 nbytes += PyBytes_GET_SIZE(c) + 1;
754 nbytes += PyBytes_GET_SIZE(c) + 1;
726 }
755 }
727 }
756 }
728
757
729 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
758 packobj = PyBytes_FromStringAndSize(NULL, nbytes);
730 if (packobj == NULL) {
759 if (packobj == NULL) {
731 goto bail;
760 goto bail;
732 }
761 }
733
762
734 p = PyBytes_AS_STRING(packobj);
763 p = PyBytes_AS_STRING(packobj);
735
764
736 pn = PyTuple_GET_ITEM(pl, 0);
765 pn = PyTuple_GET_ITEM(pl, 0);
737 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
766 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
738 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
767 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
739 goto bail;
768 goto bail;
740 }
769 }
741 memcpy(p, s, l);
770 memcpy(p, s, l);
742 p += 20;
771 p += 20;
743 pn = PyTuple_GET_ITEM(pl, 1);
772 pn = PyTuple_GET_ITEM(pl, 1);
744 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
773 if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
745 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
774 PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
746 goto bail;
775 goto bail;
747 }
776 }
748 memcpy(p, s, l);
777 memcpy(p, s, l);
749 p += 20;
778 p += 20;
750
779
751 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
780 for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
752 dirstateItemObject *tuple;
781 dirstateItemObject *tuple;
753 char state;
782 char state;
754 int mode, size, mtime;
783 int mode, size, mtime;
755 Py_ssize_t len, l;
784 Py_ssize_t len, l;
756 PyObject *o;
785 PyObject *o;
757 char *t;
786 char *t;
758
787
759 if (!dirstate_tuple_check(v)) {
788 if (!dirstate_tuple_check(v)) {
760 PyErr_SetString(PyExc_TypeError,
789 PyErr_SetString(PyExc_TypeError,
761 "expected a dirstate tuple");
790 "expected a dirstate tuple");
762 goto bail;
791 goto bail;
763 }
792 }
764 tuple = (dirstateItemObject *)v;
793 tuple = (dirstateItemObject *)v;
765
794
766 state = dirstate_item_c_v1_state(tuple);
795 state = dirstate_item_c_v1_state(tuple);
767 mode = dirstate_item_c_v1_mode(tuple);
796 mode = dirstate_item_c_v1_mode(tuple);
768 size = dirstate_item_c_v1_size(tuple);
797 size = dirstate_item_c_v1_size(tuple);
769 mtime = dirstate_item_c_v1_mtime(tuple);
798 mtime = dirstate_item_c_v1_mtime(tuple);
770 if (state == 'n' && mtime == now) {
799 if (state == 'n' && mtime == now) {
771 /* See pure/parsers.py:pack_dirstate for why we do
800 /* See pure/parsers.py:pack_dirstate for why we do
772 * this. */
801 * this. */
773 mtime = -1;
802 mtime = -1;
774 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
803 mtime_unset = (PyObject *)dirstate_item_from_v1_data(
775 state, mode, size, mtime);
804 state, mode, size, mtime);
776 if (!mtime_unset) {
805 if (!mtime_unset) {
777 goto bail;
806 goto bail;
778 }
807 }
779 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
808 if (PyDict_SetItem(map, k, mtime_unset) == -1) {
780 goto bail;
809 goto bail;
781 }
810 }
782 Py_DECREF(mtime_unset);
811 Py_DECREF(mtime_unset);
783 mtime_unset = NULL;
812 mtime_unset = NULL;
784 }
813 }
785 *p++ = state;
814 *p++ = state;
786 putbe32((uint32_t)mode, p);
815 putbe32((uint32_t)mode, p);
787 putbe32((uint32_t)size, p + 4);
816 putbe32((uint32_t)size, p + 4);
788 putbe32((uint32_t)mtime, p + 8);
817 putbe32((uint32_t)mtime, p + 8);
789 t = p + 12;
818 t = p + 12;
790 p += 16;
819 p += 16;
791 len = PyBytes_GET_SIZE(k);
820 len = PyBytes_GET_SIZE(k);
792 memcpy(p, PyBytes_AS_STRING(k), len);
821 memcpy(p, PyBytes_AS_STRING(k), len);
793 p += len;
822 p += len;
794 o = PyDict_GetItem(copymap, k);
823 o = PyDict_GetItem(copymap, k);
795 if (o) {
824 if (o) {
796 *p++ = '\0';
825 *p++ = '\0';
797 l = PyBytes_GET_SIZE(o);
826 l = PyBytes_GET_SIZE(o);
798 memcpy(p, PyBytes_AS_STRING(o), l);
827 memcpy(p, PyBytes_AS_STRING(o), l);
799 p += l;
828 p += l;
800 len += l + 1;
829 len += l + 1;
801 }
830 }
802 putbe32((uint32_t)len, t);
831 putbe32((uint32_t)len, t);
803 }
832 }
804
833
805 pos = p - PyBytes_AS_STRING(packobj);
834 pos = p - PyBytes_AS_STRING(packobj);
806 if (pos != nbytes) {
835 if (pos != nbytes) {
807 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
836 PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
808 (long)pos, (long)nbytes);
837 (long)pos, (long)nbytes);
809 goto bail;
838 goto bail;
810 }
839 }
811
840
812 return packobj;
841 return packobj;
813 bail:
842 bail:
814 Py_XDECREF(mtime_unset);
843 Py_XDECREF(mtime_unset);
815 Py_XDECREF(packobj);
844 Py_XDECREF(packobj);
816 Py_XDECREF(v);
845 Py_XDECREF(v);
817 return NULL;
846 return NULL;
818 }
847 }
819
848
820 #define BUMPED_FIX 1
849 #define BUMPED_FIX 1
821 #define USING_SHA_256 2
850 #define USING_SHA_256 2
822 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
851 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
823
852
824 static PyObject *readshas(const char *source, unsigned char num,
853 static PyObject *readshas(const char *source, unsigned char num,
825 Py_ssize_t hashwidth)
854 Py_ssize_t hashwidth)
826 {
855 {
827 int i;
856 int i;
828 PyObject *list = PyTuple_New(num);
857 PyObject *list = PyTuple_New(num);
829 if (list == NULL) {
858 if (list == NULL) {
830 return NULL;
859 return NULL;
831 }
860 }
832 for (i = 0; i < num; i++) {
861 for (i = 0; i < num; i++) {
833 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
862 PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
834 if (hash == NULL) {
863 if (hash == NULL) {
835 Py_DECREF(list);
864 Py_DECREF(list);
836 return NULL;
865 return NULL;
837 }
866 }
838 PyTuple_SET_ITEM(list, i, hash);
867 PyTuple_SET_ITEM(list, i, hash);
839 source += hashwidth;
868 source += hashwidth;
840 }
869 }
841 return list;
870 return list;
842 }
871 }
843
872
844 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
873 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
845 uint32_t *msize)
874 uint32_t *msize)
846 {
875 {
847 const char *data = databegin;
876 const char *data = databegin;
848 const char *meta;
877 const char *meta;
849
878
850 double mtime;
879 double mtime;
851 int16_t tz;
880 int16_t tz;
852 uint16_t flags;
881 uint16_t flags;
853 unsigned char nsuccs, nparents, nmetadata;
882 unsigned char nsuccs, nparents, nmetadata;
854 Py_ssize_t hashwidth = 20;
883 Py_ssize_t hashwidth = 20;
855
884
856 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
885 PyObject *prec = NULL, *parents = NULL, *succs = NULL;
857 PyObject *metadata = NULL, *ret = NULL;
886 PyObject *metadata = NULL, *ret = NULL;
858 int i;
887 int i;
859
888
860 if (data + FM1_HEADER_SIZE > dataend) {
889 if (data + FM1_HEADER_SIZE > dataend) {
861 goto overflow;
890 goto overflow;
862 }
891 }
863
892
864 *msize = getbe32(data);
893 *msize = getbe32(data);
865 data += 4;
894 data += 4;
866 mtime = getbefloat64(data);
895 mtime = getbefloat64(data);
867 data += 8;
896 data += 8;
868 tz = getbeint16(data);
897 tz = getbeint16(data);
869 data += 2;
898 data += 2;
870 flags = getbeuint16(data);
899 flags = getbeuint16(data);
871 data += 2;
900 data += 2;
872
901
873 if (flags & USING_SHA_256) {
902 if (flags & USING_SHA_256) {
874 hashwidth = 32;
903 hashwidth = 32;
875 }
904 }
876
905
877 nsuccs = (unsigned char)(*data++);
906 nsuccs = (unsigned char)(*data++);
878 nparents = (unsigned char)(*data++);
907 nparents = (unsigned char)(*data++);
879 nmetadata = (unsigned char)(*data++);
908 nmetadata = (unsigned char)(*data++);
880
909
881 if (databegin + *msize > dataend) {
910 if (databegin + *msize > dataend) {
882 goto overflow;
911 goto overflow;
883 }
912 }
884 dataend = databegin + *msize; /* narrow down to marker size */
913 dataend = databegin + *msize; /* narrow down to marker size */
885
914
886 if (data + hashwidth > dataend) {
915 if (data + hashwidth > dataend) {
887 goto overflow;
916 goto overflow;
888 }
917 }
889 prec = PyBytes_FromStringAndSize(data, hashwidth);
918 prec = PyBytes_FromStringAndSize(data, hashwidth);
890 data += hashwidth;
919 data += hashwidth;
891 if (prec == NULL) {
920 if (prec == NULL) {
892 goto bail;
921 goto bail;
893 }
922 }
894
923
895 if (data + nsuccs * hashwidth > dataend) {
924 if (data + nsuccs * hashwidth > dataend) {
896 goto overflow;
925 goto overflow;
897 }
926 }
898 succs = readshas(data, nsuccs, hashwidth);
927 succs = readshas(data, nsuccs, hashwidth);
899 if (succs == NULL) {
928 if (succs == NULL) {
900 goto bail;
929 goto bail;
901 }
930 }
902 data += nsuccs * hashwidth;
931 data += nsuccs * hashwidth;
903
932
904 if (nparents == 1 || nparents == 2) {
933 if (nparents == 1 || nparents == 2) {
905 if (data + nparents * hashwidth > dataend) {
934 if (data + nparents * hashwidth > dataend) {
906 goto overflow;
935 goto overflow;
907 }
936 }
908 parents = readshas(data, nparents, hashwidth);
937 parents = readshas(data, nparents, hashwidth);
909 if (parents == NULL) {
938 if (parents == NULL) {
910 goto bail;
939 goto bail;
911 }
940 }
912 data += nparents * hashwidth;
941 data += nparents * hashwidth;
913 } else {
942 } else {
914 parents = Py_None;
943 parents = Py_None;
915 Py_INCREF(parents);
944 Py_INCREF(parents);
916 }
945 }
917
946
918 if (data + 2 * nmetadata > dataend) {
947 if (data + 2 * nmetadata > dataend) {
919 goto overflow;
948 goto overflow;
920 }
949 }
921 meta = data + (2 * nmetadata);
950 meta = data + (2 * nmetadata);
922 metadata = PyTuple_New(nmetadata);
951 metadata = PyTuple_New(nmetadata);
923 if (metadata == NULL) {
952 if (metadata == NULL) {
924 goto bail;
953 goto bail;
925 }
954 }
926 for (i = 0; i < nmetadata; i++) {
955 for (i = 0; i < nmetadata; i++) {
927 PyObject *tmp, *left = NULL, *right = NULL;
956 PyObject *tmp, *left = NULL, *right = NULL;
928 Py_ssize_t leftsize = (unsigned char)(*data++);
957 Py_ssize_t leftsize = (unsigned char)(*data++);
929 Py_ssize_t rightsize = (unsigned char)(*data++);
958 Py_ssize_t rightsize = (unsigned char)(*data++);
930 if (meta + leftsize + rightsize > dataend) {
959 if (meta + leftsize + rightsize > dataend) {
931 goto overflow;
960 goto overflow;
932 }
961 }
933 left = PyBytes_FromStringAndSize(meta, leftsize);
962 left = PyBytes_FromStringAndSize(meta, leftsize);
934 meta += leftsize;
963 meta += leftsize;
935 right = PyBytes_FromStringAndSize(meta, rightsize);
964 right = PyBytes_FromStringAndSize(meta, rightsize);
936 meta += rightsize;
965 meta += rightsize;
937 tmp = PyTuple_New(2);
966 tmp = PyTuple_New(2);
938 if (!left || !right || !tmp) {
967 if (!left || !right || !tmp) {
939 Py_XDECREF(left);
968 Py_XDECREF(left);
940 Py_XDECREF(right);
969 Py_XDECREF(right);
941 Py_XDECREF(tmp);
970 Py_XDECREF(tmp);
942 goto bail;
971 goto bail;
943 }
972 }
944 PyTuple_SET_ITEM(tmp, 0, left);
973 PyTuple_SET_ITEM(tmp, 0, left);
945 PyTuple_SET_ITEM(tmp, 1, right);
974 PyTuple_SET_ITEM(tmp, 1, right);
946 PyTuple_SET_ITEM(metadata, i, tmp);
975 PyTuple_SET_ITEM(metadata, i, tmp);
947 }
976 }
948 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
977 ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
949 (int)tz * 60, parents);
978 (int)tz * 60, parents);
950 goto bail; /* return successfully */
979 goto bail; /* return successfully */
951
980
952 overflow:
981 overflow:
953 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
982 PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
954 bail:
983 bail:
955 Py_XDECREF(prec);
984 Py_XDECREF(prec);
956 Py_XDECREF(succs);
985 Py_XDECREF(succs);
957 Py_XDECREF(metadata);
986 Py_XDECREF(metadata);
958 Py_XDECREF(parents);
987 Py_XDECREF(parents);
959 return ret;
988 return ret;
960 }
989 }
961
990
962 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
991 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
963 {
992 {
964 const char *data, *dataend;
993 const char *data, *dataend;
965 Py_ssize_t datalen, offset, stop;
994 Py_ssize_t datalen, offset, stop;
966 PyObject *markers = NULL;
995 PyObject *markers = NULL;
967
996
968 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
997 if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
969 &offset, &stop)) {
998 &offset, &stop)) {
970 return NULL;
999 return NULL;
971 }
1000 }
972 if (offset < 0) {
1001 if (offset < 0) {
973 PyErr_SetString(PyExc_ValueError,
1002 PyErr_SetString(PyExc_ValueError,
974 "invalid negative offset in fm1readmarkers");
1003 "invalid negative offset in fm1readmarkers");
975 return NULL;
1004 return NULL;
976 }
1005 }
977 if (stop > datalen) {
1006 if (stop > datalen) {
978 PyErr_SetString(
1007 PyErr_SetString(
979 PyExc_ValueError,
1008 PyExc_ValueError,
980 "stop longer than data length in fm1readmarkers");
1009 "stop longer than data length in fm1readmarkers");
981 return NULL;
1010 return NULL;
982 }
1011 }
983 dataend = data + datalen;
1012 dataend = data + datalen;
984 data += offset;
1013 data += offset;
985 markers = PyList_New(0);
1014 markers = PyList_New(0);
986 if (!markers) {
1015 if (!markers) {
987 return NULL;
1016 return NULL;
988 }
1017 }
989 while (offset < stop) {
1018 while (offset < stop) {
990 uint32_t msize;
1019 uint32_t msize;
991 int error;
1020 int error;
992 PyObject *record = fm1readmarker(data, dataend, &msize);
1021 PyObject *record = fm1readmarker(data, dataend, &msize);
993 if (!record) {
1022 if (!record) {
994 goto bail;
1023 goto bail;
995 }
1024 }
996 error = PyList_Append(markers, record);
1025 error = PyList_Append(markers, record);
997 Py_DECREF(record);
1026 Py_DECREF(record);
998 if (error) {
1027 if (error) {
999 goto bail;
1028 goto bail;
1000 }
1029 }
1001 data += msize;
1030 data += msize;
1002 offset += msize;
1031 offset += msize;
1003 }
1032 }
1004 return markers;
1033 return markers;
1005 bail:
1034 bail:
1006 Py_DECREF(markers);
1035 Py_DECREF(markers);
1007 return NULL;
1036 return NULL;
1008 }
1037 }
1009
1038
1010 static char parsers_doc[] = "Efficient content parsing.";
1039 static char parsers_doc[] = "Efficient content parsing.";
1011
1040
1012 PyObject *encodedir(PyObject *self, PyObject *args);
1041 PyObject *encodedir(PyObject *self, PyObject *args);
1013 PyObject *pathencode(PyObject *self, PyObject *args);
1042 PyObject *pathencode(PyObject *self, PyObject *args);
1014 PyObject *lowerencode(PyObject *self, PyObject *args);
1043 PyObject *lowerencode(PyObject *self, PyObject *args);
1015 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1044 PyObject *parse_index2(PyObject *self, PyObject *args, PyObject *kwargs);
1016
1045
1017 static PyMethodDef methods[] = {
1046 static PyMethodDef methods[] = {
1018 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1047 {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
1019 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1048 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
1020 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1049 {"parse_index2", (PyCFunction)parse_index2, METH_VARARGS | METH_KEYWORDS,
1021 "parse a revlog index\n"},
1050 "parse a revlog index\n"},
1022 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1051 {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
1023 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1052 {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
1024 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1053 {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
1025 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1054 {"dict_new_presized", dict_new_presized, METH_VARARGS,
1026 "construct a dict with an expected size\n"},
1055 "construct a dict with an expected size\n"},
1027 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1056 {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
1028 "make file foldmap\n"},
1057 "make file foldmap\n"},
1029 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1058 {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
1030 "escape a UTF-8 byte string to JSON (fast path)\n"},
1059 "escape a UTF-8 byte string to JSON (fast path)\n"},
1031 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1060 {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
1032 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1061 {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
1033 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1062 {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
1034 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1063 {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
1035 "parse v1 obsolete markers\n"},
1064 "parse v1 obsolete markers\n"},
1036 {NULL, NULL}};
1065 {NULL, NULL}};
1037
1066
1038 void dirs_module_init(PyObject *mod);
1067 void dirs_module_init(PyObject *mod);
1039 void manifest_module_init(PyObject *mod);
1068 void manifest_module_init(PyObject *mod);
1040 void revlog_module_init(PyObject *mod);
1069 void revlog_module_init(PyObject *mod);
1041
1070
1042 static const int version = 20;
1071 static const int version = 20;
1043
1072
1044 static void module_init(PyObject *mod)
1073 static void module_init(PyObject *mod)
1045 {
1074 {
1046 PyModule_AddIntConstant(mod, "version", version);
1075 PyModule_AddIntConstant(mod, "version", version);
1047
1076
1048 /* This module constant has two purposes. First, it lets us unit test
1077 /* This module constant has two purposes. First, it lets us unit test
1049 * the ImportError raised without hard-coding any error text. This
1078 * the ImportError raised without hard-coding any error text. This
1050 * means we can change the text in the future without breaking tests,
1079 * means we can change the text in the future without breaking tests,
1051 * even across changesets without a recompile. Second, its presence
1080 * even across changesets without a recompile. Second, its presence
1052 * can be used to determine whether the version-checking logic is
1081 * can be used to determine whether the version-checking logic is
1053 * present, which also helps in testing across changesets without a
1082 * present, which also helps in testing across changesets without a
1054 * recompile. Note that this means the pure-Python version of parsers
1083 * recompile. Note that this means the pure-Python version of parsers
1055 * should not have this module constant. */
1084 * should not have this module constant. */
1056 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1085 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1057
1086
1058 dirs_module_init(mod);
1087 dirs_module_init(mod);
1059 manifest_module_init(mod);
1088 manifest_module_init(mod);
1060 revlog_module_init(mod);
1089 revlog_module_init(mod);
1061
1090
1062 if (PyType_Ready(&dirstateItemType) < 0) {
1091 if (PyType_Ready(&dirstateItemType) < 0) {
1063 return;
1092 return;
1064 }
1093 }
1065 Py_INCREF(&dirstateItemType);
1094 Py_INCREF(&dirstateItemType);
1066 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1095 PyModule_AddObject(mod, "DirstateItem", (PyObject *)&dirstateItemType);
1067 }
1096 }
1068
1097
1069 static int check_python_version(void)
1098 static int check_python_version(void)
1070 {
1099 {
1071 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1100 PyObject *sys = PyImport_ImportModule("sys"), *ver;
1072 long hexversion;
1101 long hexversion;
1073 if (!sys) {
1102 if (!sys) {
1074 return -1;
1103 return -1;
1075 }
1104 }
1076 ver = PyObject_GetAttrString(sys, "hexversion");
1105 ver = PyObject_GetAttrString(sys, "hexversion");
1077 Py_DECREF(sys);
1106 Py_DECREF(sys);
1078 if (!ver) {
1107 if (!ver) {
1079 return -1;
1108 return -1;
1080 }
1109 }
1081 hexversion = PyInt_AsLong(ver);
1110 hexversion = PyInt_AsLong(ver);
1082 Py_DECREF(ver);
1111 Py_DECREF(ver);
1083 /* sys.hexversion is a 32-bit number by default, so the -1 case
1112 /* sys.hexversion is a 32-bit number by default, so the -1 case
1084 * should only occur in unusual circumstances (e.g. if sys.hexversion
1113 * should only occur in unusual circumstances (e.g. if sys.hexversion
1085 * is manually set to an invalid value). */
1114 * is manually set to an invalid value). */
1086 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1115 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1087 PyErr_Format(PyExc_ImportError,
1116 PyErr_Format(PyExc_ImportError,
1088 "%s: The Mercurial extension "
1117 "%s: The Mercurial extension "
1089 "modules were compiled with Python " PY_VERSION
1118 "modules were compiled with Python " PY_VERSION
1090 ", but "
1119 ", but "
1091 "Mercurial is currently using Python with "
1120 "Mercurial is currently using Python with "
1092 "sys.hexversion=%ld: "
1121 "sys.hexversion=%ld: "
1093 "Python %s\n at: %s",
1122 "Python %s\n at: %s",
1094 versionerrortext, hexversion, Py_GetVersion(),
1123 versionerrortext, hexversion, Py_GetVersion(),
1095 Py_GetProgramFullPath());
1124 Py_GetProgramFullPath());
1096 return -1;
1125 return -1;
1097 }
1126 }
1098 return 0;
1127 return 0;
1099 }
1128 }
1100
1129
1101 #ifdef IS_PY3K
1130 #ifdef IS_PY3K
1102 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1131 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
1103 parsers_doc, -1, methods};
1132 parsers_doc, -1, methods};
1104
1133
1105 PyMODINIT_FUNC PyInit_parsers(void)
1134 PyMODINIT_FUNC PyInit_parsers(void)
1106 {
1135 {
1107 PyObject *mod;
1136 PyObject *mod;
1108
1137
1109 if (check_python_version() == -1)
1138 if (check_python_version() == -1)
1110 return NULL;
1139 return NULL;
1111 mod = PyModule_Create(&parsers_module);
1140 mod = PyModule_Create(&parsers_module);
1112 module_init(mod);
1141 module_init(mod);
1113 return mod;
1142 return mod;
1114 }
1143 }
1115 #else
1144 #else
1116 PyMODINIT_FUNC initparsers(void)
1145 PyMODINIT_FUNC initparsers(void)
1117 {
1146 {
1118 PyObject *mod;
1147 PyObject *mod;
1119
1148
1120 if (check_python_version() == -1) {
1149 if (check_python_version() == -1) {
1121 return;
1150 return;
1122 }
1151 }
1123 mod = Py_InitModule3("parsers", methods, parsers_doc);
1152 mod = Py_InitModule3("parsers", methods, parsers_doc);
1124 module_init(mod);
1153 module_init(mod);
1125 }
1154 }
1126 #endif
1155 #endif
@@ -1,80 +1,82 b''
1 /*
1 /*
2 util.h - utility functions for interfacing with the various python APIs.
2 util.h - utility functions for interfacing with the various python APIs.
3
3
4 This software may be used and distributed according to the terms of
4 This software may be used and distributed according to the terms of
5 the GNU General Public License, incorporated herein by reference.
5 the GNU General Public License, incorporated herein by reference.
6 */
6 */
7
7
8 #ifndef _HG_UTIL_H_
8 #ifndef _HG_UTIL_H_
9 #define _HG_UTIL_H_
9 #define _HG_UTIL_H_
10
10
11 #include "compat.h"
11 #include "compat.h"
12
12
13 #if PY_MAJOR_VERSION >= 3
13 #if PY_MAJOR_VERSION >= 3
14 #define IS_PY3K
14 #define IS_PY3K
15 #endif
15 #endif
16
16
17 /* helper to switch things like string literal depending on Python version */
17 /* helper to switch things like string literal depending on Python version */
18 #ifdef IS_PY3K
18 #ifdef IS_PY3K
19 #define PY23(py2, py3) py3
19 #define PY23(py2, py3) py3
20 #else
20 #else
21 #define PY23(py2, py3) py2
21 #define PY23(py2, py3) py2
22 #endif
22 #endif
23
23
24 /* clang-format off */
24 /* clang-format off */
25 typedef struct {
25 typedef struct {
26 PyObject_HEAD
26 PyObject_HEAD
27 unsigned char flags;
27 unsigned char flags;
28 int mode;
28 int mode;
29 int size;
29 int size;
30 int mtime;
30 int mtime;
31 } dirstateItemObject;
31 } dirstateItemObject;
32 /* clang-format on */
32 /* clang-format on */
33
33
34 static const unsigned char dirstate_flag_wc_tracked = 1;
34 static const unsigned char dirstate_flag_wc_tracked = 1;
35 static const unsigned char dirstate_flag_p1_tracked = 1 << 1;
35 static const unsigned char dirstate_flag_p1_tracked = 1 << 1;
36 static const unsigned char dirstate_flag_p2_info = 1 << 2;
36 static const unsigned char dirstate_flag_p2_info = 1 << 2;
37 static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3;
37 static const unsigned char dirstate_flag_has_meaningful_data = 1 << 3;
38 static const unsigned char dirstate_flag_has_meaningful_mtime = 1 << 4;
38 static const unsigned char dirstate_flag_has_meaningful_mtime = 1 << 4;
39 static const unsigned char dirstate_flag_mode_exec_perm = 1 << 5;
40 static const unsigned char dirstate_flag_mode_is_symlink = 1 << 6;
39
41
40 extern PyTypeObject dirstateItemType;
42 extern PyTypeObject dirstateItemType;
41 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateItemType)
43 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateItemType)
42
44
43 #ifndef MIN
45 #ifndef MIN
44 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
46 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
45 #endif
47 #endif
46 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
48 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
47 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
49 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
48 #define true 1
50 #define true 1
49 #define false 0
51 #define false 0
50 typedef unsigned char bool;
52 typedef unsigned char bool;
51 #else
53 #else
52 #include <stdbool.h>
54 #include <stdbool.h>
53 #endif
55 #endif
54
56
55 static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
57 static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
56 {
58 {
57 /* _PyDict_NewPresized expects a minused parameter, but it actually
59 /* _PyDict_NewPresized expects a minused parameter, but it actually
58 creates a dictionary that's the nearest power of two bigger than the
60 creates a dictionary that's the nearest power of two bigger than the
59 parameter. For example, with the initial minused = 1000, the
61 parameter. For example, with the initial minused = 1000, the
60 dictionary created has size 1024. Of course in a lot of cases that
62 dictionary created has size 1024. Of course in a lot of cases that
61 can be greater than the maximum load factor Python's dict object
63 can be greater than the maximum load factor Python's dict object
62 expects (= 2/3), so as soon as we cross the threshold we'll resize
64 expects (= 2/3), so as soon as we cross the threshold we'll resize
63 anyway. So create a dictionary that's at least 3/2 the size. */
65 anyway. So create a dictionary that's at least 3/2 the size. */
64 return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
66 return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
65 }
67 }
66
68
67 /* Convert a PyInt or PyLong to a long. Returns false if there is an
69 /* Convert a PyInt or PyLong to a long. Returns false if there is an
68 error, in which case an exception will already have been set. */
70 error, in which case an exception will already have been set. */
69 static inline bool pylong_to_long(PyObject *pylong, long *out)
71 static inline bool pylong_to_long(PyObject *pylong, long *out)
70 {
72 {
71 *out = PyLong_AsLong(pylong);
73 *out = PyLong_AsLong(pylong);
72 /* Fast path to avoid hitting PyErr_Occurred if the value was obviously
74 /* Fast path to avoid hitting PyErr_Occurred if the value was obviously
73 * not an error. */
75 * not an error. */
74 if (*out != -1) {
76 if (*out != -1) {
75 return true;
77 return true;
76 }
78 }
77 return PyErr_Occurred() == NULL;
79 return PyErr_Occurred() == NULL;
78 }
80 }
79
81
80 #endif /* _HG_UTIL_H_ */
82 #endif /* _HG_UTIL_H_ */
@@ -1,731 +1,732 b''
1 # dirstatemap.py
1 # dirstatemap.py
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import errno
8 import errno
9
9
10 from .i18n import _
10 from .i18n import _
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 pathutil,
14 pathutil,
15 policy,
15 policy,
16 pycompat,
16 pycompat,
17 txnutil,
17 txnutil,
18 util,
18 util,
19 )
19 )
20
20
21 from .dirstateutils import (
21 from .dirstateutils import (
22 docket as docketmod,
22 docket as docketmod,
23 v2,
23 )
24 )
24
25
25 parsers = policy.importmod('parsers')
26 parsers = policy.importmod('parsers')
26 rustmod = policy.importrust('dirstate')
27 rustmod = policy.importrust('dirstate')
27
28
28 propertycache = util.propertycache
29 propertycache = util.propertycache
29
30
30 if rustmod is None:
31 if rustmod is None:
31 DirstateItem = parsers.DirstateItem
32 DirstateItem = parsers.DirstateItem
32 else:
33 else:
33 DirstateItem = rustmod.DirstateItem
34 DirstateItem = rustmod.DirstateItem
34
35
35 rangemask = 0x7FFFFFFF
36 rangemask = 0x7FFFFFFF
36
37
37
38
38 class _dirstatemapcommon(object):
39 class _dirstatemapcommon(object):
39 """
40 """
40 Methods that are identical for both implementations of the dirstatemap
41 Methods that are identical for both implementations of the dirstatemap
41 class, with and without Rust extensions enabled.
42 class, with and without Rust extensions enabled.
42 """
43 """
43
44
44 # please pytype
45 # please pytype
45
46
46 _map = None
47 _map = None
47 copymap = None
48 copymap = None
48
49
49 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
50 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
50 self._use_dirstate_v2 = use_dirstate_v2
51 self._use_dirstate_v2 = use_dirstate_v2
51 self._nodeconstants = nodeconstants
52 self._nodeconstants = nodeconstants
52 self._ui = ui
53 self._ui = ui
53 self._opener = opener
54 self._opener = opener
54 self._root = root
55 self._root = root
55 self._filename = b'dirstate'
56 self._filename = b'dirstate'
56 self._nodelen = 20 # Also update Rust code when changing this!
57 self._nodelen = 20 # Also update Rust code when changing this!
57 self._parents = None
58 self._parents = None
58 self._dirtyparents = False
59 self._dirtyparents = False
59 self._docket = None
60 self._docket = None
60
61
61 # for consistent view between _pl() and _read() invocations
62 # for consistent view between _pl() and _read() invocations
62 self._pendingmode = None
63 self._pendingmode = None
63
64
64 def preload(self):
65 def preload(self):
65 """Loads the underlying data, if it's not already loaded"""
66 """Loads the underlying data, if it's not already loaded"""
66 self._map
67 self._map
67
68
68 def get(self, key, default=None):
69 def get(self, key, default=None):
69 return self._map.get(key, default)
70 return self._map.get(key, default)
70
71
71 def __len__(self):
72 def __len__(self):
72 return len(self._map)
73 return len(self._map)
73
74
74 def __iter__(self):
75 def __iter__(self):
75 return iter(self._map)
76 return iter(self._map)
76
77
77 def __contains__(self, key):
78 def __contains__(self, key):
78 return key in self._map
79 return key in self._map
79
80
80 def __getitem__(self, item):
81 def __getitem__(self, item):
81 return self._map[item]
82 return self._map[item]
82
83
83 ### sub-class utility method
84 ### sub-class utility method
84 #
85 #
85 # Use to allow for generic implementation of some method while still coping
86 # Use to allow for generic implementation of some method while still coping
86 # with minor difference between implementation.
87 # with minor difference between implementation.
87
88
88 def _dirs_incr(self, filename, old_entry=None):
89 def _dirs_incr(self, filename, old_entry=None):
89 """incremente the dirstate counter if applicable
90 """incremente the dirstate counter if applicable
90
91
91 This might be a no-op for some subclass who deal with directory
92 This might be a no-op for some subclass who deal with directory
92 tracking in a different way.
93 tracking in a different way.
93 """
94 """
94
95
95 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
96 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
96 """decremente the dirstate counter if applicable
97 """decremente the dirstate counter if applicable
97
98
98 This might be a no-op for some subclass who deal with directory
99 This might be a no-op for some subclass who deal with directory
99 tracking in a different way.
100 tracking in a different way.
100 """
101 """
101
102
102 def _refresh_entry(self, f, entry):
103 def _refresh_entry(self, f, entry):
103 """record updated state of an entry"""
104 """record updated state of an entry"""
104
105
105 def _insert_entry(self, f, entry):
106 def _insert_entry(self, f, entry):
106 """add a new dirstate entry (or replace an unrelated one)
107 """add a new dirstate entry (or replace an unrelated one)
107
108
108 The fact it is actually new is the responsability of the caller
109 The fact it is actually new is the responsability of the caller
109 """
110 """
110
111
111 def _drop_entry(self, f):
112 def _drop_entry(self, f):
112 """remove any entry for file f
113 """remove any entry for file f
113
114
114 This should also drop associated copy information
115 This should also drop associated copy information
115
116
116 The fact we actually need to drop it is the responsability of the caller"""
117 The fact we actually need to drop it is the responsability of the caller"""
117
118
118 ### method to manipulate the entries
119 ### method to manipulate the entries
119
120
120 def set_possibly_dirty(self, filename):
121 def set_possibly_dirty(self, filename):
121 """record that the current state of the file on disk is unknown"""
122 """record that the current state of the file on disk is unknown"""
122 entry = self[filename]
123 entry = self[filename]
123 entry.set_possibly_dirty()
124 entry.set_possibly_dirty()
124 self._refresh_entry(filename, entry)
125 self._refresh_entry(filename, entry)
125
126
126 def set_clean(self, filename, mode, size, mtime):
127 def set_clean(self, filename, mode, size, mtime):
127 """mark a file as back to a clean state"""
128 """mark a file as back to a clean state"""
128 entry = self[filename]
129 entry = self[filename]
129 mtime = mtime & rangemask
130 mtime = mtime & rangemask
130 size = size & rangemask
131 size = size & rangemask
131 entry.set_clean(mode, size, mtime)
132 entry.set_clean(mode, size, mtime)
132 self._refresh_entry(filename, entry)
133 self._refresh_entry(filename, entry)
133 self.copymap.pop(filename, None)
134 self.copymap.pop(filename, None)
134
135
135 def set_tracked(self, filename):
136 def set_tracked(self, filename):
136 new = False
137 new = False
137 entry = self.get(filename)
138 entry = self.get(filename)
138 if entry is None:
139 if entry is None:
139 self._dirs_incr(filename)
140 self._dirs_incr(filename)
140 entry = DirstateItem(
141 entry = DirstateItem(
141 wc_tracked=True,
142 wc_tracked=True,
142 )
143 )
143
144
144 self._insert_entry(filename, entry)
145 self._insert_entry(filename, entry)
145 new = True
146 new = True
146 elif not entry.tracked:
147 elif not entry.tracked:
147 self._dirs_incr(filename, entry)
148 self._dirs_incr(filename, entry)
148 entry.set_tracked()
149 entry.set_tracked()
149 self._refresh_entry(filename, entry)
150 self._refresh_entry(filename, entry)
150 new = True
151 new = True
151 else:
152 else:
152 # XXX This is probably overkill for more case, but we need this to
153 # XXX This is probably overkill for more case, but we need this to
153 # fully replace the `normallookup` call with `set_tracked` one.
154 # fully replace the `normallookup` call with `set_tracked` one.
154 # Consider smoothing this in the future.
155 # Consider smoothing this in the future.
155 entry.set_possibly_dirty()
156 entry.set_possibly_dirty()
156 self._refresh_entry(filename, entry)
157 self._refresh_entry(filename, entry)
157 return new
158 return new
158
159
159 def set_untracked(self, f):
160 def set_untracked(self, f):
160 """Mark a file as no longer tracked in the dirstate map"""
161 """Mark a file as no longer tracked in the dirstate map"""
161 entry = self.get(f)
162 entry = self.get(f)
162 if entry is None:
163 if entry is None:
163 return False
164 return False
164 else:
165 else:
165 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
166 self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
166 if not entry.p2_info:
167 if not entry.p2_info:
167 self.copymap.pop(f, None)
168 self.copymap.pop(f, None)
168 entry.set_untracked()
169 entry.set_untracked()
169 self._refresh_entry(f, entry)
170 self._refresh_entry(f, entry)
170 return True
171 return True
171
172
172 def reset_state(
173 def reset_state(
173 self,
174 self,
174 filename,
175 filename,
175 wc_tracked=False,
176 wc_tracked=False,
176 p1_tracked=False,
177 p1_tracked=False,
177 p2_info=False,
178 p2_info=False,
178 has_meaningful_mtime=True,
179 has_meaningful_mtime=True,
179 has_meaningful_data=True,
180 has_meaningful_data=True,
180 parentfiledata=None,
181 parentfiledata=None,
181 ):
182 ):
182 """Set a entry to a given state, diregarding all previous state
183 """Set a entry to a given state, diregarding all previous state
183
184
184 This is to be used by the part of the dirstate API dedicated to
185 This is to be used by the part of the dirstate API dedicated to
185 adjusting the dirstate after a update/merge.
186 adjusting the dirstate after a update/merge.
186
187
187 note: calling this might result to no entry existing at all if the
188 note: calling this might result to no entry existing at all if the
188 dirstate map does not see any point at having one for this file
189 dirstate map does not see any point at having one for this file
189 anymore.
190 anymore.
190 """
191 """
191 # copy information are now outdated
192 # copy information are now outdated
192 # (maybe new information should be in directly passed to this function)
193 # (maybe new information should be in directly passed to this function)
193 self.copymap.pop(filename, None)
194 self.copymap.pop(filename, None)
194
195
195 if not (p1_tracked or p2_info or wc_tracked):
196 if not (p1_tracked or p2_info or wc_tracked):
196 old_entry = self._map.get(filename)
197 old_entry = self._map.get(filename)
197 self._drop_entry(filename)
198 self._drop_entry(filename)
198 self._dirs_decr(filename, old_entry=old_entry)
199 self._dirs_decr(filename, old_entry=old_entry)
199 return
200 return
200
201
201 old_entry = self._map.get(filename)
202 old_entry = self._map.get(filename)
202 self._dirs_incr(filename, old_entry)
203 self._dirs_incr(filename, old_entry)
203 entry = DirstateItem(
204 entry = DirstateItem(
204 wc_tracked=wc_tracked,
205 wc_tracked=wc_tracked,
205 p1_tracked=p1_tracked,
206 p1_tracked=p1_tracked,
206 p2_info=p2_info,
207 p2_info=p2_info,
207 has_meaningful_mtime=has_meaningful_mtime,
208 has_meaningful_mtime=has_meaningful_mtime,
208 parentfiledata=parentfiledata,
209 parentfiledata=parentfiledata,
209 )
210 )
210 self._insert_entry(filename, entry)
211 self._insert_entry(filename, entry)
211
212
212 ### disk interaction
213 ### disk interaction
213
214
214 def _opendirstatefile(self):
215 def _opendirstatefile(self):
215 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
216 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
216 if self._pendingmode is not None and self._pendingmode != mode:
217 if self._pendingmode is not None and self._pendingmode != mode:
217 fp.close()
218 fp.close()
218 raise error.Abort(
219 raise error.Abort(
219 _(b'working directory state may be changed parallelly')
220 _(b'working directory state may be changed parallelly')
220 )
221 )
221 self._pendingmode = mode
222 self._pendingmode = mode
222 return fp
223 return fp
223
224
224 def _readdirstatefile(self, size=-1):
225 def _readdirstatefile(self, size=-1):
225 try:
226 try:
226 with self._opendirstatefile() as fp:
227 with self._opendirstatefile() as fp:
227 return fp.read(size)
228 return fp.read(size)
228 except IOError as err:
229 except IOError as err:
229 if err.errno != errno.ENOENT:
230 if err.errno != errno.ENOENT:
230 raise
231 raise
231 # File doesn't exist, so the current state is empty
232 # File doesn't exist, so the current state is empty
232 return b''
233 return b''
233
234
234 @property
235 @property
235 def docket(self):
236 def docket(self):
236 if not self._docket:
237 if not self._docket:
237 if not self._use_dirstate_v2:
238 if not self._use_dirstate_v2:
238 raise error.ProgrammingError(
239 raise error.ProgrammingError(
239 b'dirstate only has a docket in v2 format'
240 b'dirstate only has a docket in v2 format'
240 )
241 )
241 self._docket = docketmod.DirstateDocket.parse(
242 self._docket = docketmod.DirstateDocket.parse(
242 self._readdirstatefile(), self._nodeconstants
243 self._readdirstatefile(), self._nodeconstants
243 )
244 )
244 return self._docket
245 return self._docket
245
246
246 def write_v2_no_append(self, tr, st, meta, packed):
247 def write_v2_no_append(self, tr, st, meta, packed):
247 old_docket = self.docket
248 old_docket = self.docket
248 new_docket = docketmod.DirstateDocket.with_new_uuid(
249 new_docket = docketmod.DirstateDocket.with_new_uuid(
249 self.parents(), len(packed), meta
250 self.parents(), len(packed), meta
250 )
251 )
251 data_filename = new_docket.data_filename()
252 data_filename = new_docket.data_filename()
252 if tr:
253 if tr:
253 tr.add(data_filename, 0)
254 tr.add(data_filename, 0)
254 self._opener.write(data_filename, packed)
255 self._opener.write(data_filename, packed)
255 # Write the new docket after the new data file has been
256 # Write the new docket after the new data file has been
256 # written. Because `st` was opened with `atomictemp=True`,
257 # written. Because `st` was opened with `atomictemp=True`,
257 # the actual `.hg/dirstate` file is only affected on close.
258 # the actual `.hg/dirstate` file is only affected on close.
258 st.write(new_docket.serialize())
259 st.write(new_docket.serialize())
259 st.close()
260 st.close()
260 # Remove the old data file after the new docket pointing to
261 # Remove the old data file after the new docket pointing to
261 # the new data file was written.
262 # the new data file was written.
262 if old_docket.uuid:
263 if old_docket.uuid:
263 data_filename = old_docket.data_filename()
264 data_filename = old_docket.data_filename()
264 unlink = lambda _tr=None: self._opener.unlink(data_filename)
265 unlink = lambda _tr=None: self._opener.unlink(data_filename)
265 if tr:
266 if tr:
266 category = b"dirstate-v2-clean-" + old_docket.uuid
267 category = b"dirstate-v2-clean-" + old_docket.uuid
267 tr.addpostclose(category, unlink)
268 tr.addpostclose(category, unlink)
268 else:
269 else:
269 unlink()
270 unlink()
270 self._docket = new_docket
271 self._docket = new_docket
271
272
272 ### reading/setting parents
273 ### reading/setting parents
273
274
274 def parents(self):
275 def parents(self):
275 if not self._parents:
276 if not self._parents:
276 if self._use_dirstate_v2:
277 if self._use_dirstate_v2:
277 self._parents = self.docket.parents
278 self._parents = self.docket.parents
278 else:
279 else:
279 read_len = self._nodelen * 2
280 read_len = self._nodelen * 2
280 st = self._readdirstatefile(read_len)
281 st = self._readdirstatefile(read_len)
281 l = len(st)
282 l = len(st)
282 if l == read_len:
283 if l == read_len:
283 self._parents = (
284 self._parents = (
284 st[: self._nodelen],
285 st[: self._nodelen],
285 st[self._nodelen : 2 * self._nodelen],
286 st[self._nodelen : 2 * self._nodelen],
286 )
287 )
287 elif l == 0:
288 elif l == 0:
288 self._parents = (
289 self._parents = (
289 self._nodeconstants.nullid,
290 self._nodeconstants.nullid,
290 self._nodeconstants.nullid,
291 self._nodeconstants.nullid,
291 )
292 )
292 else:
293 else:
293 raise error.Abort(
294 raise error.Abort(
294 _(b'working directory state appears damaged!')
295 _(b'working directory state appears damaged!')
295 )
296 )
296
297
297 return self._parents
298 return self._parents
298
299
299
300
300 class dirstatemap(_dirstatemapcommon):
301 class dirstatemap(_dirstatemapcommon):
301 """Map encapsulating the dirstate's contents.
302 """Map encapsulating the dirstate's contents.
302
303
303 The dirstate contains the following state:
304 The dirstate contains the following state:
304
305
305 - `identity` is the identity of the dirstate file, which can be used to
306 - `identity` is the identity of the dirstate file, which can be used to
306 detect when changes have occurred to the dirstate file.
307 detect when changes have occurred to the dirstate file.
307
308
308 - `parents` is a pair containing the parents of the working copy. The
309 - `parents` is a pair containing the parents of the working copy. The
309 parents are updated by calling `setparents`.
310 parents are updated by calling `setparents`.
310
311
311 - the state map maps filenames to tuples of (state, mode, size, mtime),
312 - the state map maps filenames to tuples of (state, mode, size, mtime),
312 where state is a single character representing 'normal', 'added',
313 where state is a single character representing 'normal', 'added',
313 'removed', or 'merged'. It is read by treating the dirstate as a
314 'removed', or 'merged'. It is read by treating the dirstate as a
314 dict. File state is updated by calling various methods (see each
315 dict. File state is updated by calling various methods (see each
315 documentation for details):
316 documentation for details):
316
317
317 - `reset_state`,
318 - `reset_state`,
318 - `set_tracked`
319 - `set_tracked`
319 - `set_untracked`
320 - `set_untracked`
320 - `set_clean`
321 - `set_clean`
321 - `set_possibly_dirty`
322 - `set_possibly_dirty`
322
323
323 - `copymap` maps destination filenames to their source filename.
324 - `copymap` maps destination filenames to their source filename.
324
325
325 The dirstate also provides the following views onto the state:
326 The dirstate also provides the following views onto the state:
326
327
327 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
328 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
328 form that they appear as in the dirstate.
329 form that they appear as in the dirstate.
329
330
330 - `dirfoldmap` is a dict mapping normalized directory names to the
331 - `dirfoldmap` is a dict mapping normalized directory names to the
331 denormalized form that they appear as in the dirstate.
332 denormalized form that they appear as in the dirstate.
332 """
333 """
333
334
334 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
335 def __init__(self, ui, opener, root, nodeconstants, use_dirstate_v2):
335 super(dirstatemap, self).__init__(
336 super(dirstatemap, self).__init__(
336 ui, opener, root, nodeconstants, use_dirstate_v2
337 ui, opener, root, nodeconstants, use_dirstate_v2
337 )
338 )
338 if self._use_dirstate_v2:
339 if self._use_dirstate_v2:
339 msg = "Dirstate V2 not supportedi"
340 msg = "Dirstate V2 not supportedi"
340 msg += "(should have detected unsupported requirement)"
341 msg += "(should have detected unsupported requirement)"
341 raise error.ProgrammingError(msg)
342 raise error.ProgrammingError(msg)
342
343
343 ### Core data storage and access
344 ### Core data storage and access
344
345
345 @propertycache
346 @propertycache
346 def _map(self):
347 def _map(self):
347 self._map = {}
348 self._map = {}
348 self.read()
349 self.read()
349 return self._map
350 return self._map
350
351
351 @propertycache
352 @propertycache
352 def copymap(self):
353 def copymap(self):
353 self.copymap = {}
354 self.copymap = {}
354 self._map
355 self._map
355 return self.copymap
356 return self.copymap
356
357
357 def clear(self):
358 def clear(self):
358 self._map.clear()
359 self._map.clear()
359 self.copymap.clear()
360 self.copymap.clear()
360 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
361 self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
361 util.clearcachedproperty(self, b"_dirs")
362 util.clearcachedproperty(self, b"_dirs")
362 util.clearcachedproperty(self, b"_alldirs")
363 util.clearcachedproperty(self, b"_alldirs")
363 util.clearcachedproperty(self, b"filefoldmap")
364 util.clearcachedproperty(self, b"filefoldmap")
364 util.clearcachedproperty(self, b"dirfoldmap")
365 util.clearcachedproperty(self, b"dirfoldmap")
365
366
366 def items(self):
367 def items(self):
367 return pycompat.iteritems(self._map)
368 return pycompat.iteritems(self._map)
368
369
369 # forward for python2,3 compat
370 # forward for python2,3 compat
370 iteritems = items
371 iteritems = items
371
372
372 def debug_iter(self, all):
373 def debug_iter(self, all):
373 """
374 """
374 Return an iterator of (filename, state, mode, size, mtime) tuples
375 Return an iterator of (filename, state, mode, size, mtime) tuples
375
376
376 `all` is unused when Rust is not enabled
377 `all` is unused when Rust is not enabled
377 """
378 """
378 for (filename, item) in self.items():
379 for (filename, item) in self.items():
379 yield (filename, item.state, item.mode, item.size, item.mtime)
380 yield (filename, item.state, item.mode, item.size, item.mtime)
380
381
381 def keys(self):
382 def keys(self):
382 return self._map.keys()
383 return self._map.keys()
383
384
384 ### reading/setting parents
385 ### reading/setting parents
385
386
386 def setparents(self, p1, p2, fold_p2=False):
387 def setparents(self, p1, p2, fold_p2=False):
387 self._parents = (p1, p2)
388 self._parents = (p1, p2)
388 self._dirtyparents = True
389 self._dirtyparents = True
389 copies = {}
390 copies = {}
390 if fold_p2:
391 if fold_p2:
391 for f, s in pycompat.iteritems(self._map):
392 for f, s in pycompat.iteritems(self._map):
392 # Discard "merged" markers when moving away from a merge state
393 # Discard "merged" markers when moving away from a merge state
393 if s.p2_info:
394 if s.p2_info:
394 source = self.copymap.pop(f, None)
395 source = self.copymap.pop(f, None)
395 if source:
396 if source:
396 copies[f] = source
397 copies[f] = source
397 s.drop_merge_data()
398 s.drop_merge_data()
398 return copies
399 return copies
399
400
400 ### disk interaction
401 ### disk interaction
401
402
402 def read(self):
403 def read(self):
403 # ignore HG_PENDING because identity is used only for writing
404 # ignore HG_PENDING because identity is used only for writing
404 self.identity = util.filestat.frompath(
405 self.identity = util.filestat.frompath(
405 self._opener.join(self._filename)
406 self._opener.join(self._filename)
406 )
407 )
407
408
408 try:
409 try:
409 fp = self._opendirstatefile()
410 fp = self._opendirstatefile()
410 try:
411 try:
411 st = fp.read()
412 st = fp.read()
412 finally:
413 finally:
413 fp.close()
414 fp.close()
414 except IOError as err:
415 except IOError as err:
415 if err.errno != errno.ENOENT:
416 if err.errno != errno.ENOENT:
416 raise
417 raise
417 return
418 return
418 if not st:
419 if not st:
419 return
420 return
420
421
421 if util.safehasattr(parsers, b'dict_new_presized'):
422 if util.safehasattr(parsers, b'dict_new_presized'):
422 # Make an estimate of the number of files in the dirstate based on
423 # Make an estimate of the number of files in the dirstate based on
423 # its size. This trades wasting some memory for avoiding costly
424 # its size. This trades wasting some memory for avoiding costly
424 # resizes. Each entry have a prefix of 17 bytes followed by one or
425 # resizes. Each entry have a prefix of 17 bytes followed by one or
425 # two path names. Studies on various large-scale real-world repositories
426 # two path names. Studies on various large-scale real-world repositories
426 # found 54 bytes a reasonable upper limit for the average path names.
427 # found 54 bytes a reasonable upper limit for the average path names.
427 # Copy entries are ignored for the sake of this estimate.
428 # Copy entries are ignored for the sake of this estimate.
428 self._map = parsers.dict_new_presized(len(st) // 71)
429 self._map = parsers.dict_new_presized(len(st) // 71)
429
430
430 # Python's garbage collector triggers a GC each time a certain number
431 # Python's garbage collector triggers a GC each time a certain number
431 # of container objects (the number being defined by
432 # of container objects (the number being defined by
432 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
433 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
433 # for each file in the dirstate. The C version then immediately marks
434 # for each file in the dirstate. The C version then immediately marks
434 # them as not to be tracked by the collector. However, this has no
435 # them as not to be tracked by the collector. However, this has no
435 # effect on when GCs are triggered, only on what objects the GC looks
436 # effect on when GCs are triggered, only on what objects the GC looks
436 # into. This means that O(number of files) GCs are unavoidable.
437 # into. This means that O(number of files) GCs are unavoidable.
437 # Depending on when in the process's lifetime the dirstate is parsed,
438 # Depending on when in the process's lifetime the dirstate is parsed,
438 # this can get very expensive. As a workaround, disable GC while
439 # this can get very expensive. As a workaround, disable GC while
439 # parsing the dirstate.
440 # parsing the dirstate.
440 #
441 #
441 # (we cannot decorate the function directly since it is in a C module)
442 # (we cannot decorate the function directly since it is in a C module)
442 parse_dirstate = util.nogc(parsers.parse_dirstate)
443 parse_dirstate = util.nogc(parsers.parse_dirstate)
443 p = parse_dirstate(self._map, self.copymap, st)
444 p = parse_dirstate(self._map, self.copymap, st)
444 if not self._dirtyparents:
445 if not self._dirtyparents:
445 self.setparents(*p)
446 self.setparents(*p)
446
447
447 # Avoid excess attribute lookups by fast pathing certain checks
448 # Avoid excess attribute lookups by fast pathing certain checks
448 self.__contains__ = self._map.__contains__
449 self.__contains__ = self._map.__contains__
449 self.__getitem__ = self._map.__getitem__
450 self.__getitem__ = self._map.__getitem__
450 self.get = self._map.get
451 self.get = self._map.get
451
452
452 def write(self, _tr, st, now):
453 def write(self, _tr, st, now):
453 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
454 d = parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
454 st.write(d)
455 st.write(d)
455 st.close()
456 st.close()
456 self._dirtyparents = False
457 self._dirtyparents = False
457
458
458 @propertycache
459 @propertycache
459 def identity(self):
460 def identity(self):
460 self._map
461 self._map
461 return self.identity
462 return self.identity
462
463
463 ### code related to maintaining and accessing "extra" property
464 ### code related to maintaining and accessing "extra" property
464 # (e.g. "has_dir")
465 # (e.g. "has_dir")
465
466
466 def _dirs_incr(self, filename, old_entry=None):
467 def _dirs_incr(self, filename, old_entry=None):
467 """incremente the dirstate counter if applicable"""
468 """incremente the dirstate counter if applicable"""
468 if (
469 if (
469 old_entry is None or old_entry.removed
470 old_entry is None or old_entry.removed
470 ) and "_dirs" in self.__dict__:
471 ) and "_dirs" in self.__dict__:
471 self._dirs.addpath(filename)
472 self._dirs.addpath(filename)
472 if old_entry is None and "_alldirs" in self.__dict__:
473 if old_entry is None and "_alldirs" in self.__dict__:
473 self._alldirs.addpath(filename)
474 self._alldirs.addpath(filename)
474
475
475 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
476 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
476 """decremente the dirstate counter if applicable"""
477 """decremente the dirstate counter if applicable"""
477 if old_entry is not None:
478 if old_entry is not None:
478 if "_dirs" in self.__dict__ and not old_entry.removed:
479 if "_dirs" in self.__dict__ and not old_entry.removed:
479 self._dirs.delpath(filename)
480 self._dirs.delpath(filename)
480 if "_alldirs" in self.__dict__ and not remove_variant:
481 if "_alldirs" in self.__dict__ and not remove_variant:
481 self._alldirs.delpath(filename)
482 self._alldirs.delpath(filename)
482 elif remove_variant and "_alldirs" in self.__dict__:
483 elif remove_variant and "_alldirs" in self.__dict__:
483 self._alldirs.addpath(filename)
484 self._alldirs.addpath(filename)
484 if "filefoldmap" in self.__dict__:
485 if "filefoldmap" in self.__dict__:
485 normed = util.normcase(filename)
486 normed = util.normcase(filename)
486 self.filefoldmap.pop(normed, None)
487 self.filefoldmap.pop(normed, None)
487
488
488 @propertycache
489 @propertycache
489 def filefoldmap(self):
490 def filefoldmap(self):
490 """Returns a dictionary mapping normalized case paths to their
491 """Returns a dictionary mapping normalized case paths to their
491 non-normalized versions.
492 non-normalized versions.
492 """
493 """
493 try:
494 try:
494 makefilefoldmap = parsers.make_file_foldmap
495 makefilefoldmap = parsers.make_file_foldmap
495 except AttributeError:
496 except AttributeError:
496 pass
497 pass
497 else:
498 else:
498 return makefilefoldmap(
499 return makefilefoldmap(
499 self._map, util.normcasespec, util.normcasefallback
500 self._map, util.normcasespec, util.normcasefallback
500 )
501 )
501
502
502 f = {}
503 f = {}
503 normcase = util.normcase
504 normcase = util.normcase
504 for name, s in pycompat.iteritems(self._map):
505 for name, s in pycompat.iteritems(self._map):
505 if not s.removed:
506 if not s.removed:
506 f[normcase(name)] = name
507 f[normcase(name)] = name
507 f[b'.'] = b'.' # prevents useless util.fspath() invocation
508 f[b'.'] = b'.' # prevents useless util.fspath() invocation
508 return f
509 return f
509
510
510 @propertycache
511 @propertycache
511 def dirfoldmap(self):
512 def dirfoldmap(self):
512 f = {}
513 f = {}
513 normcase = util.normcase
514 normcase = util.normcase
514 for name in self._dirs:
515 for name in self._dirs:
515 f[normcase(name)] = name
516 f[normcase(name)] = name
516 return f
517 return f
517
518
518 def hastrackeddir(self, d):
519 def hastrackeddir(self, d):
519 """
520 """
520 Returns True if the dirstate contains a tracked (not removed) file
521 Returns True if the dirstate contains a tracked (not removed) file
521 in this directory.
522 in this directory.
522 """
523 """
523 return d in self._dirs
524 return d in self._dirs
524
525
525 def hasdir(self, d):
526 def hasdir(self, d):
526 """
527 """
527 Returns True if the dirstate contains a file (tracked or removed)
528 Returns True if the dirstate contains a file (tracked or removed)
528 in this directory.
529 in this directory.
529 """
530 """
530 return d in self._alldirs
531 return d in self._alldirs
531
532
532 @propertycache
533 @propertycache
533 def _dirs(self):
534 def _dirs(self):
534 return pathutil.dirs(self._map, only_tracked=True)
535 return pathutil.dirs(self._map, only_tracked=True)
535
536
536 @propertycache
537 @propertycache
537 def _alldirs(self):
538 def _alldirs(self):
538 return pathutil.dirs(self._map)
539 return pathutil.dirs(self._map)
539
540
540 ### code related to manipulation of entries and copy-sources
541 ### code related to manipulation of entries and copy-sources
541
542
542 def _refresh_entry(self, f, entry):
543 def _refresh_entry(self, f, entry):
543 if not entry.any_tracked:
544 if not entry.any_tracked:
544 self._map.pop(f, None)
545 self._map.pop(f, None)
545
546
546 def _insert_entry(self, f, entry):
547 def _insert_entry(self, f, entry):
547 self._map[f] = entry
548 self._map[f] = entry
548
549
549 def _drop_entry(self, f):
550 def _drop_entry(self, f):
550 self._map.pop(f, None)
551 self._map.pop(f, None)
551 self.copymap.pop(f, None)
552 self.copymap.pop(f, None)
552
553
553
554
554 if rustmod is not None:
555 if rustmod is not None:
555
556
556 class dirstatemap(_dirstatemapcommon):
557 class dirstatemap(_dirstatemapcommon):
557
558
558 ### Core data storage and access
559 ### Core data storage and access
559
560
560 @propertycache
561 @propertycache
561 def _map(self):
562 def _map(self):
562 """
563 """
563 Fills the Dirstatemap when called.
564 Fills the Dirstatemap when called.
564 """
565 """
565 # ignore HG_PENDING because identity is used only for writing
566 # ignore HG_PENDING because identity is used only for writing
566 self.identity = util.filestat.frompath(
567 self.identity = util.filestat.frompath(
567 self._opener.join(self._filename)
568 self._opener.join(self._filename)
568 )
569 )
569
570
570 if self._use_dirstate_v2:
571 if self._use_dirstate_v2:
571 if self.docket.uuid:
572 if self.docket.uuid:
572 # TODO: use mmap when possible
573 # TODO: use mmap when possible
573 data = self._opener.read(self.docket.data_filename())
574 data = self._opener.read(self.docket.data_filename())
574 else:
575 else:
575 data = b''
576 data = b''
576 self._map = rustmod.DirstateMap.new_v2(
577 self._map = rustmod.DirstateMap.new_v2(
577 data, self.docket.data_size, self.docket.tree_metadata
578 data, self.docket.data_size, self.docket.tree_metadata
578 )
579 )
579 parents = self.docket.parents
580 parents = self.docket.parents
580 else:
581 else:
581 self._map, parents = rustmod.DirstateMap.new_v1(
582 self._map, parents = rustmod.DirstateMap.new_v1(
582 self._readdirstatefile()
583 self._readdirstatefile()
583 )
584 )
584
585
585 if parents and not self._dirtyparents:
586 if parents and not self._dirtyparents:
586 self.setparents(*parents)
587 self.setparents(*parents)
587
588
588 self.__contains__ = self._map.__contains__
589 self.__contains__ = self._map.__contains__
589 self.__getitem__ = self._map.__getitem__
590 self.__getitem__ = self._map.__getitem__
590 self.get = self._map.get
591 self.get = self._map.get
591 return self._map
592 return self._map
592
593
593 @property
594 @property
594 def copymap(self):
595 def copymap(self):
595 return self._map.copymap()
596 return self._map.copymap()
596
597
597 def debug_iter(self, all):
598 def debug_iter(self, all):
598 """
599 """
599 Return an iterator of (filename, state, mode, size, mtime) tuples
600 Return an iterator of (filename, state, mode, size, mtime) tuples
600
601
601 `all`: also include with `state == b' '` dirstate tree nodes that
602 `all`: also include with `state == b' '` dirstate tree nodes that
602 don't have an associated `DirstateItem`.
603 don't have an associated `DirstateItem`.
603
604
604 """
605 """
605 return self._map.debug_iter(all)
606 return self._map.debug_iter(all)
606
607
607 def clear(self):
608 def clear(self):
608 self._map.clear()
609 self._map.clear()
609 self.setparents(
610 self.setparents(
610 self._nodeconstants.nullid, self._nodeconstants.nullid
611 self._nodeconstants.nullid, self._nodeconstants.nullid
611 )
612 )
612 util.clearcachedproperty(self, b"_dirs")
613 util.clearcachedproperty(self, b"_dirs")
613 util.clearcachedproperty(self, b"_alldirs")
614 util.clearcachedproperty(self, b"_alldirs")
614 util.clearcachedproperty(self, b"dirfoldmap")
615 util.clearcachedproperty(self, b"dirfoldmap")
615
616
616 def items(self):
617 def items(self):
617 return self._map.items()
618 return self._map.items()
618
619
619 # forward for python2,3 compat
620 # forward for python2,3 compat
620 iteritems = items
621 iteritems = items
621
622
622 def keys(self):
623 def keys(self):
623 return iter(self._map)
624 return iter(self._map)
624
625
625 ### reading/setting parents
626 ### reading/setting parents
626
627
627 def setparents(self, p1, p2, fold_p2=False):
628 def setparents(self, p1, p2, fold_p2=False):
628 self._parents = (p1, p2)
629 self._parents = (p1, p2)
629 self._dirtyparents = True
630 self._dirtyparents = True
630 copies = {}
631 copies = {}
631 if fold_p2:
632 if fold_p2:
632 # Collect into an intermediate list to avoid a `RuntimeError`
633 # Collect into an intermediate list to avoid a `RuntimeError`
633 # exception due to mutation during iteration.
634 # exception due to mutation during iteration.
634 # TODO: move this the whole loop to Rust where `iter_mut`
635 # TODO: move this the whole loop to Rust where `iter_mut`
635 # enables in-place mutation of elements of a collection while
636 # enables in-place mutation of elements of a collection while
636 # iterating it, without mutating the collection itself.
637 # iterating it, without mutating the collection itself.
637 files_with_p2_info = [
638 files_with_p2_info = [
638 f for f, s in self._map.items() if s.p2_info
639 f for f, s in self._map.items() if s.p2_info
639 ]
640 ]
640 rust_map = self._map
641 rust_map = self._map
641 for f in files_with_p2_info:
642 for f in files_with_p2_info:
642 e = rust_map.get(f)
643 e = rust_map.get(f)
643 source = self.copymap.pop(f, None)
644 source = self.copymap.pop(f, None)
644 if source:
645 if source:
645 copies[f] = source
646 copies[f] = source
646 e.drop_merge_data()
647 e.drop_merge_data()
647 rust_map.set_dirstate_item(f, e)
648 rust_map.set_dirstate_item(f, e)
648 return copies
649 return copies
649
650
650 ### disk interaction
651 ### disk interaction
651
652
652 @propertycache
653 @propertycache
653 def identity(self):
654 def identity(self):
654 self._map
655 self._map
655 return self.identity
656 return self.identity
656
657
657 def write(self, tr, st, now):
658 def write(self, tr, st, now):
658 if not self._use_dirstate_v2:
659 if not self._use_dirstate_v2:
659 p1, p2 = self.parents()
660 p1, p2 = self.parents()
660 packed = self._map.write_v1(p1, p2, now)
661 packed = self._map.write_v1(p1, p2, now)
661 st.write(packed)
662 st.write(packed)
662 st.close()
663 st.close()
663 self._dirtyparents = False
664 self._dirtyparents = False
664 return
665 return
665
666
666 # We can only append to an existing data file if there is one
667 # We can only append to an existing data file if there is one
667 can_append = self.docket.uuid is not None
668 can_append = self.docket.uuid is not None
668 packed, meta, append = self._map.write_v2(now, can_append)
669 packed, meta, append = self._map.write_v2(now, can_append)
669 if append:
670 if append:
670 docket = self.docket
671 docket = self.docket
671 data_filename = docket.data_filename()
672 data_filename = docket.data_filename()
672 if tr:
673 if tr:
673 tr.add(data_filename, docket.data_size)
674 tr.add(data_filename, docket.data_size)
674 with self._opener(data_filename, b'r+b') as fp:
675 with self._opener(data_filename, b'r+b') as fp:
675 fp.seek(docket.data_size)
676 fp.seek(docket.data_size)
676 assert fp.tell() == docket.data_size
677 assert fp.tell() == docket.data_size
677 written = fp.write(packed)
678 written = fp.write(packed)
678 if written is not None: # py2 may return None
679 if written is not None: # py2 may return None
679 assert written == len(packed), (written, len(packed))
680 assert written == len(packed), (written, len(packed))
680 docket.data_size += len(packed)
681 docket.data_size += len(packed)
681 docket.parents = self.parents()
682 docket.parents = self.parents()
682 docket.tree_metadata = meta
683 docket.tree_metadata = meta
683 st.write(docket.serialize())
684 st.write(docket.serialize())
684 st.close()
685 st.close()
685 else:
686 else:
686 self.write_v2_no_append(tr, st, meta, packed)
687 self.write_v2_no_append(tr, st, meta, packed)
687 # Reload from the newly-written file
688 # Reload from the newly-written file
688 util.clearcachedproperty(self, b"_map")
689 util.clearcachedproperty(self, b"_map")
689 self._dirtyparents = False
690 self._dirtyparents = False
690
691
691 ### code related to maintaining and accessing "extra" property
692 ### code related to maintaining and accessing "extra" property
692 # (e.g. "has_dir")
693 # (e.g. "has_dir")
693
694
694 @propertycache
695 @propertycache
695 def filefoldmap(self):
696 def filefoldmap(self):
696 """Returns a dictionary mapping normalized case paths to their
697 """Returns a dictionary mapping normalized case paths to their
697 non-normalized versions.
698 non-normalized versions.
698 """
699 """
699 return self._map.filefoldmapasdict()
700 return self._map.filefoldmapasdict()
700
701
701 def hastrackeddir(self, d):
702 def hastrackeddir(self, d):
702 return self._map.hastrackeddir(d)
703 return self._map.hastrackeddir(d)
703
704
704 def hasdir(self, d):
705 def hasdir(self, d):
705 return self._map.hasdir(d)
706 return self._map.hasdir(d)
706
707
707 @propertycache
708 @propertycache
708 def dirfoldmap(self):
709 def dirfoldmap(self):
709 f = {}
710 f = {}
710 normcase = util.normcase
711 normcase = util.normcase
711 for name in self._map.tracked_dirs():
712 for name in self._map.tracked_dirs():
712 f[normcase(name)] = name
713 f[normcase(name)] = name
713 return f
714 return f
714
715
715 ### code related to manipulation of entries and copy-sources
716 ### code related to manipulation of entries and copy-sources
716
717
717 def _refresh_entry(self, f, entry):
718 def _refresh_entry(self, f, entry):
718 if not entry.any_tracked:
719 if not entry.any_tracked:
719 self._map.drop_item_and_copy_source(f)
720 self._map.drop_item_and_copy_source(f)
720 else:
721 else:
721 self._map.addfile(f, entry)
722 self._map.addfile(f, entry)
722
723
723 def _insert_entry(self, f, entry):
724 def _insert_entry(self, f, entry):
724 self._map.addfile(f, entry)
725 self._map.addfile(f, entry)
725
726
726 def _drop_entry(self, f):
727 def _drop_entry(self, f):
727 self._map.drop_item_and_copy_source(f)
728 self._map.drop_item_and_copy_source(f)
728
729
729 def __setitem__(self, key, value):
730 def __setitem__(self, key, value):
730 assert isinstance(value, DirstateItem)
731 assert isinstance(value, DirstateItem)
731 self._map.set_dirstate_item(key, value)
732 self._map.set_dirstate_item(key, value)
@@ -1,75 +1,71 b''
1 # dirstatedocket.py - docket file for dirstate-v2
1 # dirstatedocket.py - docket file for dirstate-v2
2 #
2 #
3 # Copyright Mercurial Contributors
3 # Copyright Mercurial Contributors
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11
11
12 from ..revlogutils import docket as docket_mod
12 from ..revlogutils import docket as docket_mod
13
13 from . import v2
14
14
15 V2_FORMAT_MARKER = b"dirstate-v2\n"
15 V2_FORMAT_MARKER = b"dirstate-v2\n"
16
16
17 # Must match the constant of the same name in
18 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
19 TREE_METADATA_SIZE = 44
20
21 # * 12 bytes: format marker
17 # * 12 bytes: format marker
22 # * 32 bytes: node ID of the working directory's first parent
18 # * 32 bytes: node ID of the working directory's first parent
23 # * 32 bytes: node ID of the working directory's second parent
19 # * 32 bytes: node ID of the working directory's second parent
24 # * {TREE_METADATA_SIZE} bytes: tree metadata, parsed separately
20 # * {TREE_METADATA_SIZE} bytes: tree metadata, parsed separately
25 # * 4 bytes: big-endian used size of the data file
21 # * 4 bytes: big-endian used size of the data file
26 # * 1 byte: length of the data file's UUID
22 # * 1 byte: length of the data file's UUID
27 # * variable: data file's UUID
23 # * variable: data file's UUID
28 #
24 #
29 # Node IDs are null-padded if shorter than 32 bytes.
25 # Node IDs are null-padded if shorter than 32 bytes.
30 # A data file shorter than the specified used size is corrupted (truncated)
26 # A data file shorter than the specified used size is corrupted (truncated)
31 HEADER = struct.Struct(
27 HEADER = struct.Struct(
32 ">{}s32s32s{}sLB".format(len(V2_FORMAT_MARKER), TREE_METADATA_SIZE)
28 ">{}s32s32s{}sLB".format(len(V2_FORMAT_MARKER), v2.TREE_METADATA_SIZE)
33 )
29 )
34
30
35
31
36 class DirstateDocket(object):
32 class DirstateDocket(object):
37 data_filename_pattern = b'dirstate.%s'
33 data_filename_pattern = b'dirstate.%s'
38
34
39 def __init__(self, parents, data_size, tree_metadata, uuid):
35 def __init__(self, parents, data_size, tree_metadata, uuid):
40 self.parents = parents
36 self.parents = parents
41 self.data_size = data_size
37 self.data_size = data_size
42 self.tree_metadata = tree_metadata
38 self.tree_metadata = tree_metadata
43 self.uuid = uuid
39 self.uuid = uuid
44
40
45 @classmethod
41 @classmethod
46 def with_new_uuid(cls, parents, data_size, tree_metadata):
42 def with_new_uuid(cls, parents, data_size, tree_metadata):
47 return cls(parents, data_size, tree_metadata, docket_mod.make_uid())
43 return cls(parents, data_size, tree_metadata, docket_mod.make_uid())
48
44
49 @classmethod
45 @classmethod
50 def parse(cls, data, nodeconstants):
46 def parse(cls, data, nodeconstants):
51 if not data:
47 if not data:
52 parents = (nodeconstants.nullid, nodeconstants.nullid)
48 parents = (nodeconstants.nullid, nodeconstants.nullid)
53 return cls(parents, 0, b'', None)
49 return cls(parents, 0, b'', None)
54 marker, p1, p2, meta, data_size, uuid_size = HEADER.unpack_from(data)
50 marker, p1, p2, meta, data_size, uuid_size = HEADER.unpack_from(data)
55 if marker != V2_FORMAT_MARKER:
51 if marker != V2_FORMAT_MARKER:
56 raise ValueError("expected dirstate-v2 marker")
52 raise ValueError("expected dirstate-v2 marker")
57 uuid = data[HEADER.size : HEADER.size + uuid_size]
53 uuid = data[HEADER.size : HEADER.size + uuid_size]
58 p1 = p1[: nodeconstants.nodelen]
54 p1 = p1[: nodeconstants.nodelen]
59 p2 = p2[: nodeconstants.nodelen]
55 p2 = p2[: nodeconstants.nodelen]
60 return cls((p1, p2), data_size, meta, uuid)
56 return cls((p1, p2), data_size, meta, uuid)
61
57
62 def serialize(self):
58 def serialize(self):
63 p1, p2 = self.parents
59 p1, p2 = self.parents
64 header = HEADER.pack(
60 header = HEADER.pack(
65 V2_FORMAT_MARKER,
61 V2_FORMAT_MARKER,
66 p1,
62 p1,
67 p2,
63 p2,
68 self.tree_metadata,
64 self.tree_metadata,
69 self.data_size,
65 self.data_size,
70 len(self.uuid),
66 len(self.uuid),
71 )
67 )
72 return header + self.uuid
68 return header + self.uuid
73
69
74 def data_filename(self):
70 def data_filename(self):
75 return self.data_filename_pattern % self.uuid
71 return self.data_filename_pattern % self.uuid
@@ -1,736 +1,770 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 struct
11 import struct
11 import zlib
12 import zlib
12
13
13 from ..node import (
14 from ..node import (
14 nullrev,
15 nullrev,
15 sha1nodeconstants,
16 sha1nodeconstants,
16 )
17 )
17 from ..thirdparty import attr
18 from ..thirdparty import attr
18 from .. import (
19 from .. import (
19 error,
20 error,
20 pycompat,
21 pycompat,
21 revlogutils,
22 revlogutils,
22 util,
23 util,
23 )
24 )
24
25
25 from ..revlogutils import nodemap as nodemaputil
26 from ..revlogutils import nodemap as nodemaputil
26 from ..revlogutils import constants as revlog_constants
27 from ..revlogutils import constants as revlog_constants
27
28
28 stringio = pycompat.bytesio
29 stringio = pycompat.bytesio
29
30
30
31
31 _pack = struct.pack
32 _pack = struct.pack
32 _unpack = struct.unpack
33 _unpack = struct.unpack
33 _compress = zlib.compress
34 _compress = zlib.compress
34 _decompress = zlib.decompress
35 _decompress = zlib.decompress
35
36
36
37
37 # 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
38 FROM_P2 = -2
39 FROM_P2 = -2
39
40
40 # 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
41 NONNORMAL = -1
42 NONNORMAL = -1
42
43
43 # 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
44 AMBIGUOUS_TIME = -1
45 AMBIGUOUS_TIME = -1
45
46
47 # Bits of the `flags` byte inside a node in the file format
48 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
49 DIRSTATE_V2_P1_TRACKED = 1 << 1
50 DIRSTATE_V2_P2_INFO = 1 << 2
51 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 3
52 DIRSTATE_V2_HAS_MTIME = 1 << 4
53 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 5
54 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 6
55
46
56
47 @attr.s(slots=True, init=False)
57 @attr.s(slots=True, init=False)
48 class DirstateItem(object):
58 class DirstateItem(object):
49 """represent a dirstate entry
59 """represent a dirstate entry
50
60
51 It hold multiple attributes
61 It hold multiple attributes
52
62
53 # about file tracking
63 # about file tracking
54 - wc_tracked: is the file tracked by the working copy
64 - wc_tracked: is the file tracked by the working copy
55 - p1_tracked: is the file tracked in working copy first parent
65 - p1_tracked: is the file tracked in working copy first parent
56 - p2_info: the file has been involved in some merge operation. Either
66 - p2_info: the file has been involved in some merge operation. Either
57 because it was actually merged, or because the p2 version was
67 because it was actually merged, or because the p2 version was
58 ahead, or because some rename moved it there. In either case
68 ahead, or because some rename moved it there. In either case
59 `hg status` will want it displayed as modified.
69 `hg status` will want it displayed as modified.
60
70
61 # about the file state expected from p1 manifest:
71 # about the file state expected from p1 manifest:
62 - mode: the file mode in p1
72 - mode: the file mode in p1
63 - size: the file size in p1
73 - size: the file size in p1
64
74
65 These value can be set to None, which mean we don't have a meaningful value
75 These value can be set to None, which mean we don't have a meaningful value
66 to compare with. Either because we don't really care about them as there
76 to compare with. Either because we don't really care about them as there
67 `status` is known without having to look at the disk or because we don't
77 `status` is known without having to look at the disk or because we don't
68 know these right now and a full comparison will be needed to find out if
78 know these right now and a full comparison will be needed to find out if
69 the file is clean.
79 the file is clean.
70
80
71 # about the file state on disk last time we saw it:
81 # about the file state on disk last time we saw it:
72 - mtime: the last known clean mtime for the file.
82 - mtime: the last known clean mtime for the file.
73
83
74 This value can be set to None if no cachable state exist. Either because we
84 This value can be set to None if no cachable state exist. Either because we
75 do not care (see previous section) or because we could not cache something
85 do not care (see previous section) or because we could not cache something
76 yet.
86 yet.
77 """
87 """
78
88
79 _wc_tracked = attr.ib()
89 _wc_tracked = attr.ib()
80 _p1_tracked = attr.ib()
90 _p1_tracked = attr.ib()
81 _p2_info = attr.ib()
91 _p2_info = attr.ib()
82 _mode = attr.ib()
92 _mode = attr.ib()
83 _size = attr.ib()
93 _size = attr.ib()
84 _mtime = attr.ib()
94 _mtime = attr.ib()
85
95
86 def __init__(
96 def __init__(
87 self,
97 self,
88 wc_tracked=False,
98 wc_tracked=False,
89 p1_tracked=False,
99 p1_tracked=False,
90 p2_info=False,
100 p2_info=False,
91 has_meaningful_data=True,
101 has_meaningful_data=True,
92 has_meaningful_mtime=True,
102 has_meaningful_mtime=True,
93 parentfiledata=None,
103 parentfiledata=None,
94 ):
104 ):
95 self._wc_tracked = wc_tracked
105 self._wc_tracked = wc_tracked
96 self._p1_tracked = p1_tracked
106 self._p1_tracked = p1_tracked
97 self._p2_info = p2_info
107 self._p2_info = p2_info
98
108
99 self._mode = None
109 self._mode = None
100 self._size = None
110 self._size = None
101 self._mtime = None
111 self._mtime = None
102 if parentfiledata is None:
112 if parentfiledata is None:
103 has_meaningful_mtime = False
113 has_meaningful_mtime = False
104 has_meaningful_data = False
114 has_meaningful_data = False
105 if has_meaningful_data:
115 if has_meaningful_data:
106 self._mode = parentfiledata[0]
116 self._mode = parentfiledata[0]
107 self._size = parentfiledata[1]
117 self._size = parentfiledata[1]
108 if has_meaningful_mtime:
118 if has_meaningful_mtime:
109 self._mtime = parentfiledata[2]
119 self._mtime = parentfiledata[2]
110
120
111 @classmethod
121 @classmethod
122 def from_v2_data(cls, flags, size, mtime):
123 """Build a new DirstateItem object from V2 data"""
124 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
125 mode = None
126 if has_mode_size:
127 assert stat.S_IXUSR == 0o100
128 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
129 mode = 0o755
130 else:
131 mode = 0o644
132 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
133 mode |= stat.S_IFLNK
134 else:
135 mode |= stat.S_IFREG
136 return cls(
137 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
138 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
139 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
140 has_meaningful_data=has_mode_size,
141 has_meaningful_mtime=bool(flags & DIRSTATE_V2_HAS_MTIME),
142 parentfiledata=(mode, size, mtime),
143 )
144
145 @classmethod
112 def from_v1_data(cls, state, mode, size, mtime):
146 def from_v1_data(cls, state, mode, size, mtime):
113 """Build a new DirstateItem object from V1 data
147 """Build a new DirstateItem object from V1 data
114
148
115 Since the dirstate-v1 format is frozen, the signature of this function
149 Since the dirstate-v1 format is frozen, the signature of this function
116 is not expected to change, unlike the __init__ one.
150 is not expected to change, unlike the __init__ one.
117 """
151 """
118 if state == b'm':
152 if state == b'm':
119 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
153 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
120 elif state == b'a':
154 elif state == b'a':
121 return cls(wc_tracked=True)
155 return cls(wc_tracked=True)
122 elif state == b'r':
156 elif state == b'r':
123 if size == NONNORMAL:
157 if size == NONNORMAL:
124 p1_tracked = True
158 p1_tracked = True
125 p2_info = True
159 p2_info = True
126 elif size == FROM_P2:
160 elif size == FROM_P2:
127 p1_tracked = False
161 p1_tracked = False
128 p2_info = True
162 p2_info = True
129 else:
163 else:
130 p1_tracked = True
164 p1_tracked = True
131 p2_info = False
165 p2_info = False
132 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
166 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
133 elif state == b'n':
167 elif state == b'n':
134 if size == FROM_P2:
168 if size == FROM_P2:
135 return cls(wc_tracked=True, p2_info=True)
169 return cls(wc_tracked=True, p2_info=True)
136 elif size == NONNORMAL:
170 elif size == NONNORMAL:
137 return cls(wc_tracked=True, p1_tracked=True)
171 return cls(wc_tracked=True, p1_tracked=True)
138 elif mtime == AMBIGUOUS_TIME:
172 elif mtime == AMBIGUOUS_TIME:
139 return cls(
173 return cls(
140 wc_tracked=True,
174 wc_tracked=True,
141 p1_tracked=True,
175 p1_tracked=True,
142 has_meaningful_mtime=False,
176 has_meaningful_mtime=False,
143 parentfiledata=(mode, size, 42),
177 parentfiledata=(mode, size, 42),
144 )
178 )
145 else:
179 else:
146 return cls(
180 return cls(
147 wc_tracked=True,
181 wc_tracked=True,
148 p1_tracked=True,
182 p1_tracked=True,
149 parentfiledata=(mode, size, mtime),
183 parentfiledata=(mode, size, mtime),
150 )
184 )
151 else:
185 else:
152 raise RuntimeError(b'unknown state: %s' % state)
186 raise RuntimeError(b'unknown state: %s' % state)
153
187
154 def set_possibly_dirty(self):
188 def set_possibly_dirty(self):
155 """Mark a file as "possibly dirty"
189 """Mark a file as "possibly dirty"
156
190
157 This means the next status call will have to actually check its content
191 This means the next status call will have to actually check its content
158 to make sure it is correct.
192 to make sure it is correct.
159 """
193 """
160 self._mtime = None
194 self._mtime = None
161
195
162 def set_clean(self, mode, size, mtime):
196 def set_clean(self, mode, size, mtime):
163 """mark a file as "clean" cancelling potential "possibly dirty call"
197 """mark a file as "clean" cancelling potential "possibly dirty call"
164
198
165 Note: this function is a descendant of `dirstate.normal` and is
199 Note: this function is a descendant of `dirstate.normal` and is
166 currently expected to be call on "normal" entry only. There are not
200 currently expected to be call on "normal" entry only. There are not
167 reason for this to not change in the future as long as the ccode is
201 reason for this to not change in the future as long as the ccode is
168 updated to preserve the proper state of the non-normal files.
202 updated to preserve the proper state of the non-normal files.
169 """
203 """
170 self._wc_tracked = True
204 self._wc_tracked = True
171 self._p1_tracked = True
205 self._p1_tracked = True
172 self._mode = mode
206 self._mode = mode
173 self._size = size
207 self._size = size
174 self._mtime = mtime
208 self._mtime = mtime
175
209
176 def set_tracked(self):
210 def set_tracked(self):
177 """mark a file as tracked in the working copy
211 """mark a file as tracked in the working copy
178
212
179 This will ultimately be called by command like `hg add`.
213 This will ultimately be called by command like `hg add`.
180 """
214 """
181 self._wc_tracked = True
215 self._wc_tracked = True
182 # `set_tracked` is replacing various `normallookup` call. So we mark
216 # `set_tracked` is replacing various `normallookup` call. So we mark
183 # the files as needing lookup
217 # the files as needing lookup
184 #
218 #
185 # Consider dropping this in the future in favor of something less broad.
219 # Consider dropping this in the future in favor of something less broad.
186 self._mtime = None
220 self._mtime = None
187
221
188 def set_untracked(self):
222 def set_untracked(self):
189 """mark a file as untracked in the working copy
223 """mark a file as untracked in the working copy
190
224
191 This will ultimately be called by command like `hg remove`.
225 This will ultimately be called by command like `hg remove`.
192 """
226 """
193 self._wc_tracked = False
227 self._wc_tracked = False
194 self._mode = None
228 self._mode = None
195 self._size = None
229 self._size = None
196 self._mtime = None
230 self._mtime = None
197
231
198 def drop_merge_data(self):
232 def drop_merge_data(self):
199 """remove all "merge-only" from a DirstateItem
233 """remove all "merge-only" from a DirstateItem
200
234
201 This is to be call by the dirstatemap code when the second parent is dropped
235 This is to be call by the dirstatemap code when the second parent is dropped
202 """
236 """
203 if self._p2_info:
237 if self._p2_info:
204 self._p2_info = False
238 self._p2_info = False
205 self._mode = None
239 self._mode = None
206 self._size = None
240 self._size = None
207 self._mtime = None
241 self._mtime = None
208
242
209 @property
243 @property
210 def mode(self):
244 def mode(self):
211 return self.v1_mode()
245 return self.v1_mode()
212
246
213 @property
247 @property
214 def size(self):
248 def size(self):
215 return self.v1_size()
249 return self.v1_size()
216
250
217 @property
251 @property
218 def mtime(self):
252 def mtime(self):
219 return self.v1_mtime()
253 return self.v1_mtime()
220
254
221 @property
255 @property
222 def state(self):
256 def state(self):
223 """
257 """
224 States are:
258 States are:
225 n normal
259 n normal
226 m needs merging
260 m needs merging
227 r marked for removal
261 r marked for removal
228 a marked for addition
262 a marked for addition
229
263
230 XXX This "state" is a bit obscure and mostly a direct expression of the
264 XXX This "state" is a bit obscure and mostly a direct expression of the
231 dirstatev1 format. It would make sense to ultimately deprecate it in
265 dirstatev1 format. It would make sense to ultimately deprecate it in
232 favor of the more "semantic" attributes.
266 favor of the more "semantic" attributes.
233 """
267 """
234 if not self.any_tracked:
268 if not self.any_tracked:
235 return b'?'
269 return b'?'
236 return self.v1_state()
270 return self.v1_state()
237
271
238 @property
272 @property
239 def tracked(self):
273 def tracked(self):
240 """True is the file is tracked in the working copy"""
274 """True is the file is tracked in the working copy"""
241 return self._wc_tracked
275 return self._wc_tracked
242
276
243 @property
277 @property
244 def any_tracked(self):
278 def any_tracked(self):
245 """True is the file is tracked anywhere (wc or parents)"""
279 """True is the file is tracked anywhere (wc or parents)"""
246 return self._wc_tracked or self._p1_tracked or self._p2_info
280 return self._wc_tracked or self._p1_tracked or self._p2_info
247
281
248 @property
282 @property
249 def added(self):
283 def added(self):
250 """True if the file has been added"""
284 """True if the file has been added"""
251 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
285 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
252
286
253 @property
287 @property
254 def maybe_clean(self):
288 def maybe_clean(self):
255 """True if the file has a chance to be in the "clean" state"""
289 """True if the file has a chance to be in the "clean" state"""
256 if not self._wc_tracked:
290 if not self._wc_tracked:
257 return False
291 return False
258 elif not self._p1_tracked:
292 elif not self._p1_tracked:
259 return False
293 return False
260 elif self._p2_info:
294 elif self._p2_info:
261 return False
295 return False
262 return True
296 return True
263
297
264 @property
298 @property
265 def p1_tracked(self):
299 def p1_tracked(self):
266 """True if the file is tracked in the first parent manifest"""
300 """True if the file is tracked in the first parent manifest"""
267 return self._p1_tracked
301 return self._p1_tracked
268
302
269 @property
303 @property
270 def p2_info(self):
304 def p2_info(self):
271 """True if the file needed to merge or apply any input from p2
305 """True if the file needed to merge or apply any input from p2
272
306
273 See the class documentation for details.
307 See the class documentation for details.
274 """
308 """
275 return self._wc_tracked and self._p2_info
309 return self._wc_tracked and self._p2_info
276
310
277 @property
311 @property
278 def removed(self):
312 def removed(self):
279 """True if the file has been removed"""
313 """True if the file has been removed"""
280 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
314 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
281
315
282 def v1_state(self):
316 def v1_state(self):
283 """return a "state" suitable for v1 serialization"""
317 """return a "state" suitable for v1 serialization"""
284 if not self.any_tracked:
318 if not self.any_tracked:
285 # the object has no state to record, this is -currently-
319 # the object has no state to record, this is -currently-
286 # unsupported
320 # unsupported
287 raise RuntimeError('untracked item')
321 raise RuntimeError('untracked item')
288 elif self.removed:
322 elif self.removed:
289 return b'r'
323 return b'r'
290 elif self._p1_tracked and self._p2_info:
324 elif self._p1_tracked and self._p2_info:
291 return b'm'
325 return b'm'
292 elif self.added:
326 elif self.added:
293 return b'a'
327 return b'a'
294 else:
328 else:
295 return b'n'
329 return b'n'
296
330
297 def v1_mode(self):
331 def v1_mode(self):
298 """return a "mode" suitable for v1 serialization"""
332 """return a "mode" suitable for v1 serialization"""
299 return self._mode if self._mode is not None else 0
333 return self._mode if self._mode is not None else 0
300
334
301 def v1_size(self):
335 def v1_size(self):
302 """return a "size" suitable for v1 serialization"""
336 """return a "size" suitable for v1 serialization"""
303 if not self.any_tracked:
337 if not self.any_tracked:
304 # the object has no state to record, this is -currently-
338 # the object has no state to record, this is -currently-
305 # unsupported
339 # unsupported
306 raise RuntimeError('untracked item')
340 raise RuntimeError('untracked item')
307 elif self.removed and self._p1_tracked and self._p2_info:
341 elif self.removed and self._p1_tracked and self._p2_info:
308 return NONNORMAL
342 return NONNORMAL
309 elif self._p2_info:
343 elif self._p2_info:
310 return FROM_P2
344 return FROM_P2
311 elif self.removed:
345 elif self.removed:
312 return 0
346 return 0
313 elif self.added:
347 elif self.added:
314 return NONNORMAL
348 return NONNORMAL
315 elif self._size is None:
349 elif self._size is None:
316 return NONNORMAL
350 return NONNORMAL
317 else:
351 else:
318 return self._size
352 return self._size
319
353
320 def v1_mtime(self):
354 def v1_mtime(self):
321 """return a "mtime" suitable for v1 serialization"""
355 """return a "mtime" suitable for v1 serialization"""
322 if not self.any_tracked:
356 if not self.any_tracked:
323 # the object has no state to record, this is -currently-
357 # the object has no state to record, this is -currently-
324 # unsupported
358 # unsupported
325 raise RuntimeError('untracked item')
359 raise RuntimeError('untracked item')
326 elif self.removed:
360 elif self.removed:
327 return 0
361 return 0
328 elif self._mtime is None:
362 elif self._mtime is None:
329 return AMBIGUOUS_TIME
363 return AMBIGUOUS_TIME
330 elif self._p2_info:
364 elif self._p2_info:
331 return AMBIGUOUS_TIME
365 return AMBIGUOUS_TIME
332 elif not self._p1_tracked:
366 elif not self._p1_tracked:
333 return AMBIGUOUS_TIME
367 return AMBIGUOUS_TIME
334 else:
368 else:
335 return self._mtime
369 return self._mtime
336
370
337 def need_delay(self, now):
371 def need_delay(self, now):
338 """True if the stored mtime would be ambiguous with the current time"""
372 """True if the stored mtime would be ambiguous with the current time"""
339 return self.v1_state() == b'n' and self.v1_mtime() == now
373 return self.v1_state() == b'n' and self.v1_mtime() == now
340
374
341
375
342 def gettype(q):
376 def gettype(q):
343 return int(q & 0xFFFF)
377 return int(q & 0xFFFF)
344
378
345
379
346 class BaseIndexObject(object):
380 class BaseIndexObject(object):
347 # Can I be passed to an algorithme implemented in Rust ?
381 # Can I be passed to an algorithme implemented in Rust ?
348 rust_ext_compat = 0
382 rust_ext_compat = 0
349 # Format of an index entry according to Python's `struct` language
383 # Format of an index entry according to Python's `struct` language
350 index_format = revlog_constants.INDEX_ENTRY_V1
384 index_format = revlog_constants.INDEX_ENTRY_V1
351 # Size of a C unsigned long long int, platform independent
385 # Size of a C unsigned long long int, platform independent
352 big_int_size = struct.calcsize(b'>Q')
386 big_int_size = struct.calcsize(b'>Q')
353 # Size of a C long int, platform independent
387 # Size of a C long int, platform independent
354 int_size = struct.calcsize(b'>i')
388 int_size = struct.calcsize(b'>i')
355 # An empty index entry, used as a default value to be overridden, or nullrev
389 # An empty index entry, used as a default value to be overridden, or nullrev
356 null_item = (
390 null_item = (
357 0,
391 0,
358 0,
392 0,
359 0,
393 0,
360 -1,
394 -1,
361 -1,
395 -1,
362 -1,
396 -1,
363 -1,
397 -1,
364 sha1nodeconstants.nullid,
398 sha1nodeconstants.nullid,
365 0,
399 0,
366 0,
400 0,
367 revlog_constants.COMP_MODE_INLINE,
401 revlog_constants.COMP_MODE_INLINE,
368 revlog_constants.COMP_MODE_INLINE,
402 revlog_constants.COMP_MODE_INLINE,
369 )
403 )
370
404
371 @util.propertycache
405 @util.propertycache
372 def entry_size(self):
406 def entry_size(self):
373 return self.index_format.size
407 return self.index_format.size
374
408
375 @property
409 @property
376 def nodemap(self):
410 def nodemap(self):
377 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
411 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
378 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
412 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
379 return self._nodemap
413 return self._nodemap
380
414
381 @util.propertycache
415 @util.propertycache
382 def _nodemap(self):
416 def _nodemap(self):
383 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
417 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
384 for r in range(0, len(self)):
418 for r in range(0, len(self)):
385 n = self[r][7]
419 n = self[r][7]
386 nodemap[n] = r
420 nodemap[n] = r
387 return nodemap
421 return nodemap
388
422
389 def has_node(self, node):
423 def has_node(self, node):
390 """return True if the node exist in the index"""
424 """return True if the node exist in the index"""
391 return node in self._nodemap
425 return node in self._nodemap
392
426
393 def rev(self, node):
427 def rev(self, node):
394 """return a revision for a node
428 """return a revision for a node
395
429
396 If the node is unknown, raise a RevlogError"""
430 If the node is unknown, raise a RevlogError"""
397 return self._nodemap[node]
431 return self._nodemap[node]
398
432
399 def get_rev(self, node):
433 def get_rev(self, node):
400 """return a revision for a node
434 """return a revision for a node
401
435
402 If the node is unknown, return None"""
436 If the node is unknown, return None"""
403 return self._nodemap.get(node)
437 return self._nodemap.get(node)
404
438
405 def _stripnodes(self, start):
439 def _stripnodes(self, start):
406 if '_nodemap' in vars(self):
440 if '_nodemap' in vars(self):
407 for r in range(start, len(self)):
441 for r in range(start, len(self)):
408 n = self[r][7]
442 n = self[r][7]
409 del self._nodemap[n]
443 del self._nodemap[n]
410
444
411 def clearcaches(self):
445 def clearcaches(self):
412 self.__dict__.pop('_nodemap', None)
446 self.__dict__.pop('_nodemap', None)
413
447
414 def __len__(self):
448 def __len__(self):
415 return self._lgt + len(self._extra)
449 return self._lgt + len(self._extra)
416
450
417 def append(self, tup):
451 def append(self, tup):
418 if '_nodemap' in vars(self):
452 if '_nodemap' in vars(self):
419 self._nodemap[tup[7]] = len(self)
453 self._nodemap[tup[7]] = len(self)
420 data = self._pack_entry(len(self), tup)
454 data = self._pack_entry(len(self), tup)
421 self._extra.append(data)
455 self._extra.append(data)
422
456
423 def _pack_entry(self, rev, entry):
457 def _pack_entry(self, rev, entry):
424 assert entry[8] == 0
458 assert entry[8] == 0
425 assert entry[9] == 0
459 assert entry[9] == 0
426 return self.index_format.pack(*entry[:8])
460 return self.index_format.pack(*entry[:8])
427
461
428 def _check_index(self, i):
462 def _check_index(self, i):
429 if not isinstance(i, int):
463 if not isinstance(i, int):
430 raise TypeError(b"expecting int indexes")
464 raise TypeError(b"expecting int indexes")
431 if i < 0 or i >= len(self):
465 if i < 0 or i >= len(self):
432 raise IndexError
466 raise IndexError
433
467
434 def __getitem__(self, i):
468 def __getitem__(self, i):
435 if i == -1:
469 if i == -1:
436 return self.null_item
470 return self.null_item
437 self._check_index(i)
471 self._check_index(i)
438 if i >= self._lgt:
472 if i >= self._lgt:
439 data = self._extra[i - self._lgt]
473 data = self._extra[i - self._lgt]
440 else:
474 else:
441 index = self._calculate_index(i)
475 index = self._calculate_index(i)
442 data = self._data[index : index + self.entry_size]
476 data = self._data[index : index + self.entry_size]
443 r = self._unpack_entry(i, data)
477 r = self._unpack_entry(i, data)
444 if self._lgt and i == 0:
478 if self._lgt and i == 0:
445 offset = revlogutils.offset_type(0, gettype(r[0]))
479 offset = revlogutils.offset_type(0, gettype(r[0]))
446 r = (offset,) + r[1:]
480 r = (offset,) + r[1:]
447 return r
481 return r
448
482
449 def _unpack_entry(self, rev, data):
483 def _unpack_entry(self, rev, data):
450 r = self.index_format.unpack(data)
484 r = self.index_format.unpack(data)
451 r = r + (
485 r = r + (
452 0,
486 0,
453 0,
487 0,
454 revlog_constants.COMP_MODE_INLINE,
488 revlog_constants.COMP_MODE_INLINE,
455 revlog_constants.COMP_MODE_INLINE,
489 revlog_constants.COMP_MODE_INLINE,
456 )
490 )
457 return r
491 return r
458
492
459 def pack_header(self, header):
493 def pack_header(self, header):
460 """pack header information as binary"""
494 """pack header information as binary"""
461 v_fmt = revlog_constants.INDEX_HEADER
495 v_fmt = revlog_constants.INDEX_HEADER
462 return v_fmt.pack(header)
496 return v_fmt.pack(header)
463
497
464 def entry_binary(self, rev):
498 def entry_binary(self, rev):
465 """return the raw binary string representing a revision"""
499 """return the raw binary string representing a revision"""
466 entry = self[rev]
500 entry = self[rev]
467 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
501 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
468 if rev == 0:
502 if rev == 0:
469 p = p[revlog_constants.INDEX_HEADER.size :]
503 p = p[revlog_constants.INDEX_HEADER.size :]
470 return p
504 return p
471
505
472
506
473 class IndexObject(BaseIndexObject):
507 class IndexObject(BaseIndexObject):
474 def __init__(self, data):
508 def __init__(self, data):
475 assert len(data) % self.entry_size == 0, (
509 assert len(data) % self.entry_size == 0, (
476 len(data),
510 len(data),
477 self.entry_size,
511 self.entry_size,
478 len(data) % self.entry_size,
512 len(data) % self.entry_size,
479 )
513 )
480 self._data = data
514 self._data = data
481 self._lgt = len(data) // self.entry_size
515 self._lgt = len(data) // self.entry_size
482 self._extra = []
516 self._extra = []
483
517
484 def _calculate_index(self, i):
518 def _calculate_index(self, i):
485 return i * self.entry_size
519 return i * self.entry_size
486
520
487 def __delitem__(self, i):
521 def __delitem__(self, i):
488 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
522 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
489 raise ValueError(b"deleting slices only supports a:-1 with step 1")
523 raise ValueError(b"deleting slices only supports a:-1 with step 1")
490 i = i.start
524 i = i.start
491 self._check_index(i)
525 self._check_index(i)
492 self._stripnodes(i)
526 self._stripnodes(i)
493 if i < self._lgt:
527 if i < self._lgt:
494 self._data = self._data[: i * self.entry_size]
528 self._data = self._data[: i * self.entry_size]
495 self._lgt = i
529 self._lgt = i
496 self._extra = []
530 self._extra = []
497 else:
531 else:
498 self._extra = self._extra[: i - self._lgt]
532 self._extra = self._extra[: i - self._lgt]
499
533
500
534
501 class PersistentNodeMapIndexObject(IndexObject):
535 class PersistentNodeMapIndexObject(IndexObject):
502 """a Debug oriented class to test persistent nodemap
536 """a Debug oriented class to test persistent nodemap
503
537
504 We need a simple python object to test API and higher level behavior. See
538 We need a simple python object to test API and higher level behavior. See
505 the Rust implementation for more serious usage. This should be used only
539 the Rust implementation for more serious usage. This should be used only
506 through the dedicated `devel.persistent-nodemap` config.
540 through the dedicated `devel.persistent-nodemap` config.
507 """
541 """
508
542
509 def nodemap_data_all(self):
543 def nodemap_data_all(self):
510 """Return bytes containing a full serialization of a nodemap
544 """Return bytes containing a full serialization of a nodemap
511
545
512 The nodemap should be valid for the full set of revisions in the
546 The nodemap should be valid for the full set of revisions in the
513 index."""
547 index."""
514 return nodemaputil.persistent_data(self)
548 return nodemaputil.persistent_data(self)
515
549
516 def nodemap_data_incremental(self):
550 def nodemap_data_incremental(self):
517 """Return bytes containing a incremental update to persistent nodemap
551 """Return bytes containing a incremental update to persistent nodemap
518
552
519 This containst the data for an append-only update of the data provided
553 This containst the data for an append-only update of the data provided
520 in the last call to `update_nodemap_data`.
554 in the last call to `update_nodemap_data`.
521 """
555 """
522 if self._nm_root is None:
556 if self._nm_root is None:
523 return None
557 return None
524 docket = self._nm_docket
558 docket = self._nm_docket
525 changed, data = nodemaputil.update_persistent_data(
559 changed, data = nodemaputil.update_persistent_data(
526 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
560 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
527 )
561 )
528
562
529 self._nm_root = self._nm_max_idx = self._nm_docket = None
563 self._nm_root = self._nm_max_idx = self._nm_docket = None
530 return docket, changed, data
564 return docket, changed, data
531
565
532 def update_nodemap_data(self, docket, nm_data):
566 def update_nodemap_data(self, docket, nm_data):
533 """provide full block of persisted binary data for a nodemap
567 """provide full block of persisted binary data for a nodemap
534
568
535 The data are expected to come from disk. See `nodemap_data_all` for a
569 The data are expected to come from disk. See `nodemap_data_all` for a
536 produceur of such data."""
570 produceur of such data."""
537 if nm_data is not None:
571 if nm_data is not None:
538 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
572 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
539 if self._nm_root:
573 if self._nm_root:
540 self._nm_docket = docket
574 self._nm_docket = docket
541 else:
575 else:
542 self._nm_root = self._nm_max_idx = self._nm_docket = None
576 self._nm_root = self._nm_max_idx = self._nm_docket = None
543
577
544
578
545 class InlinedIndexObject(BaseIndexObject):
579 class InlinedIndexObject(BaseIndexObject):
546 def __init__(self, data, inline=0):
580 def __init__(self, data, inline=0):
547 self._data = data
581 self._data = data
548 self._lgt = self._inline_scan(None)
582 self._lgt = self._inline_scan(None)
549 self._inline_scan(self._lgt)
583 self._inline_scan(self._lgt)
550 self._extra = []
584 self._extra = []
551
585
552 def _inline_scan(self, lgt):
586 def _inline_scan(self, lgt):
553 off = 0
587 off = 0
554 if lgt is not None:
588 if lgt is not None:
555 self._offsets = [0] * lgt
589 self._offsets = [0] * lgt
556 count = 0
590 count = 0
557 while off <= len(self._data) - self.entry_size:
591 while off <= len(self._data) - self.entry_size:
558 start = off + self.big_int_size
592 start = off + self.big_int_size
559 (s,) = struct.unpack(
593 (s,) = struct.unpack(
560 b'>i',
594 b'>i',
561 self._data[start : start + self.int_size],
595 self._data[start : start + self.int_size],
562 )
596 )
563 if lgt is not None:
597 if lgt is not None:
564 self._offsets[count] = off
598 self._offsets[count] = off
565 count += 1
599 count += 1
566 off += self.entry_size + s
600 off += self.entry_size + s
567 if off != len(self._data):
601 if off != len(self._data):
568 raise ValueError(b"corrupted data")
602 raise ValueError(b"corrupted data")
569 return count
603 return count
570
604
571 def __delitem__(self, i):
605 def __delitem__(self, i):
572 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
606 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
573 raise ValueError(b"deleting slices only supports a:-1 with step 1")
607 raise ValueError(b"deleting slices only supports a:-1 with step 1")
574 i = i.start
608 i = i.start
575 self._check_index(i)
609 self._check_index(i)
576 self._stripnodes(i)
610 self._stripnodes(i)
577 if i < self._lgt:
611 if i < self._lgt:
578 self._offsets = self._offsets[:i]
612 self._offsets = self._offsets[:i]
579 self._lgt = i
613 self._lgt = i
580 self._extra = []
614 self._extra = []
581 else:
615 else:
582 self._extra = self._extra[: i - self._lgt]
616 self._extra = self._extra[: i - self._lgt]
583
617
584 def _calculate_index(self, i):
618 def _calculate_index(self, i):
585 return self._offsets[i]
619 return self._offsets[i]
586
620
587
621
588 def parse_index2(data, inline, revlogv2=False):
622 def parse_index2(data, inline, revlogv2=False):
589 if not inline:
623 if not inline:
590 cls = IndexObject2 if revlogv2 else IndexObject
624 cls = IndexObject2 if revlogv2 else IndexObject
591 return cls(data), None
625 return cls(data), None
592 cls = InlinedIndexObject
626 cls = InlinedIndexObject
593 return cls(data, inline), (0, data)
627 return cls(data, inline), (0, data)
594
628
595
629
596 def parse_index_cl_v2(data):
630 def parse_index_cl_v2(data):
597 return IndexChangelogV2(data), None
631 return IndexChangelogV2(data), None
598
632
599
633
600 class IndexObject2(IndexObject):
634 class IndexObject2(IndexObject):
601 index_format = revlog_constants.INDEX_ENTRY_V2
635 index_format = revlog_constants.INDEX_ENTRY_V2
602
636
603 def replace_sidedata_info(
637 def replace_sidedata_info(
604 self,
638 self,
605 rev,
639 rev,
606 sidedata_offset,
640 sidedata_offset,
607 sidedata_length,
641 sidedata_length,
608 offset_flags,
642 offset_flags,
609 compression_mode,
643 compression_mode,
610 ):
644 ):
611 """
645 """
612 Replace an existing index entry's sidedata offset and length with new
646 Replace an existing index entry's sidedata offset and length with new
613 ones.
647 ones.
614 This cannot be used outside of the context of sidedata rewriting,
648 This cannot be used outside of the context of sidedata rewriting,
615 inside the transaction that creates the revision `rev`.
649 inside the transaction that creates the revision `rev`.
616 """
650 """
617 if rev < 0:
651 if rev < 0:
618 raise KeyError
652 raise KeyError
619 self._check_index(rev)
653 self._check_index(rev)
620 if rev < self._lgt:
654 if rev < self._lgt:
621 msg = b"cannot rewrite entries outside of this transaction"
655 msg = b"cannot rewrite entries outside of this transaction"
622 raise KeyError(msg)
656 raise KeyError(msg)
623 else:
657 else:
624 entry = list(self[rev])
658 entry = list(self[rev])
625 entry[0] = offset_flags
659 entry[0] = offset_flags
626 entry[8] = sidedata_offset
660 entry[8] = sidedata_offset
627 entry[9] = sidedata_length
661 entry[9] = sidedata_length
628 entry[11] = compression_mode
662 entry[11] = compression_mode
629 entry = tuple(entry)
663 entry = tuple(entry)
630 new = self._pack_entry(rev, entry)
664 new = self._pack_entry(rev, entry)
631 self._extra[rev - self._lgt] = new
665 self._extra[rev - self._lgt] = new
632
666
633 def _unpack_entry(self, rev, data):
667 def _unpack_entry(self, rev, data):
634 data = self.index_format.unpack(data)
668 data = self.index_format.unpack(data)
635 entry = data[:10]
669 entry = data[:10]
636 data_comp = data[10] & 3
670 data_comp = data[10] & 3
637 sidedata_comp = (data[10] & (3 << 2)) >> 2
671 sidedata_comp = (data[10] & (3 << 2)) >> 2
638 return entry + (data_comp, sidedata_comp)
672 return entry + (data_comp, sidedata_comp)
639
673
640 def _pack_entry(self, rev, entry):
674 def _pack_entry(self, rev, entry):
641 data = entry[:10]
675 data = entry[:10]
642 data_comp = entry[10] & 3
676 data_comp = entry[10] & 3
643 sidedata_comp = (entry[11] & 3) << 2
677 sidedata_comp = (entry[11] & 3) << 2
644 data += (data_comp | sidedata_comp,)
678 data += (data_comp | sidedata_comp,)
645
679
646 return self.index_format.pack(*data)
680 return self.index_format.pack(*data)
647
681
648 def entry_binary(self, rev):
682 def entry_binary(self, rev):
649 """return the raw binary string representing a revision"""
683 """return the raw binary string representing a revision"""
650 entry = self[rev]
684 entry = self[rev]
651 return self._pack_entry(rev, entry)
685 return self._pack_entry(rev, entry)
652
686
653 def pack_header(self, header):
687 def pack_header(self, header):
654 """pack header information as binary"""
688 """pack header information as binary"""
655 msg = 'version header should go in the docket, not the index: %d'
689 msg = 'version header should go in the docket, not the index: %d'
656 msg %= header
690 msg %= header
657 raise error.ProgrammingError(msg)
691 raise error.ProgrammingError(msg)
658
692
659
693
660 class IndexChangelogV2(IndexObject2):
694 class IndexChangelogV2(IndexObject2):
661 index_format = revlog_constants.INDEX_ENTRY_CL_V2
695 index_format = revlog_constants.INDEX_ENTRY_CL_V2
662
696
663 def _unpack_entry(self, rev, data, r=True):
697 def _unpack_entry(self, rev, data, r=True):
664 items = self.index_format.unpack(data)
698 items = self.index_format.unpack(data)
665 entry = items[:3] + (rev, rev) + items[3:8]
699 entry = items[:3] + (rev, rev) + items[3:8]
666 data_comp = items[8] & 3
700 data_comp = items[8] & 3
667 sidedata_comp = (items[8] >> 2) & 3
701 sidedata_comp = (items[8] >> 2) & 3
668 return entry + (data_comp, sidedata_comp)
702 return entry + (data_comp, sidedata_comp)
669
703
670 def _pack_entry(self, rev, entry):
704 def _pack_entry(self, rev, entry):
671 assert entry[3] == rev, entry[3]
705 assert entry[3] == rev, entry[3]
672 assert entry[4] == rev, entry[4]
706 assert entry[4] == rev, entry[4]
673 data = entry[:3] + entry[5:10]
707 data = entry[:3] + entry[5:10]
674 data_comp = entry[10] & 3
708 data_comp = entry[10] & 3
675 sidedata_comp = (entry[11] & 3) << 2
709 sidedata_comp = (entry[11] & 3) << 2
676 data += (data_comp | sidedata_comp,)
710 data += (data_comp | sidedata_comp,)
677 return self.index_format.pack(*data)
711 return self.index_format.pack(*data)
678
712
679
713
680 def parse_index_devel_nodemap(data, inline):
714 def parse_index_devel_nodemap(data, inline):
681 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
715 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
682 return PersistentNodeMapIndexObject(data), None
716 return PersistentNodeMapIndexObject(data), None
683
717
684
718
685 def parse_dirstate(dmap, copymap, st):
719 def parse_dirstate(dmap, copymap, st):
686 parents = [st[:20], st[20:40]]
720 parents = [st[:20], st[20:40]]
687 # dereference fields so they will be local in loop
721 # dereference fields so they will be local in loop
688 format = b">cllll"
722 format = b">cllll"
689 e_size = struct.calcsize(format)
723 e_size = struct.calcsize(format)
690 pos1 = 40
724 pos1 = 40
691 l = len(st)
725 l = len(st)
692
726
693 # the inner loop
727 # the inner loop
694 while pos1 < l:
728 while pos1 < l:
695 pos2 = pos1 + e_size
729 pos2 = pos1 + e_size
696 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
730 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
697 pos1 = pos2 + e[4]
731 pos1 = pos2 + e[4]
698 f = st[pos2:pos1]
732 f = st[pos2:pos1]
699 if b'\0' in f:
733 if b'\0' in f:
700 f, c = f.split(b'\0')
734 f, c = f.split(b'\0')
701 copymap[f] = c
735 copymap[f] = c
702 dmap[f] = DirstateItem.from_v1_data(*e[:4])
736 dmap[f] = DirstateItem.from_v1_data(*e[:4])
703 return parents
737 return parents
704
738
705
739
706 def pack_dirstate(dmap, copymap, pl, now):
740 def pack_dirstate(dmap, copymap, pl, now):
707 now = int(now)
741 now = int(now)
708 cs = stringio()
742 cs = stringio()
709 write = cs.write
743 write = cs.write
710 write(b"".join(pl))
744 write(b"".join(pl))
711 for f, e in pycompat.iteritems(dmap):
745 for f, e in pycompat.iteritems(dmap):
712 if e.need_delay(now):
746 if e.need_delay(now):
713 # The file was last modified "simultaneously" with the current
747 # The file was last modified "simultaneously" with the current
714 # write to dirstate (i.e. within the same second for file-
748 # write to dirstate (i.e. within the same second for file-
715 # systems with a granularity of 1 sec). This commonly happens
749 # systems with a granularity of 1 sec). This commonly happens
716 # for at least a couple of files on 'update'.
750 # for at least a couple of files on 'update'.
717 # The user could change the file without changing its size
751 # The user could change the file without changing its size
718 # within the same second. Invalidate the file's mtime in
752 # within the same second. Invalidate the file's mtime in
719 # dirstate, forcing future 'status' calls to compare the
753 # dirstate, forcing future 'status' calls to compare the
720 # contents of the file if the size is the same. This prevents
754 # contents of the file if the size is the same. This prevents
721 # mistakenly treating such files as clean.
755 # mistakenly treating such files as clean.
722 e.set_possibly_dirty()
756 e.set_possibly_dirty()
723
757
724 if f in copymap:
758 if f in copymap:
725 f = b"%s\0%s" % (f, copymap[f])
759 f = b"%s\0%s" % (f, copymap[f])
726 e = _pack(
760 e = _pack(
727 b">cllll",
761 b">cllll",
728 e.v1_state(),
762 e.v1_state(),
729 e.v1_mode(),
763 e.v1_mode(),
730 e.v1_size(),
764 e.v1_size(),
731 e.v1_mtime(),
765 e.v1_mtime(),
732 len(f),
766 len(f),
733 )
767 )
734 write(e)
768 write(e)
735 write(f)
769 write(f)
736 return cs.getvalue()
770 return cs.getvalue()
@@ -1,720 +1,718 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::TruncatedTimestamp;
5 use crate::dirstate::TruncatedTimestamp;
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
7 use crate::dirstate_tree::path_with_basename::WithBasename;
7 use crate::dirstate_tree::path_with_basename::WithBasename;
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::DirstateEntry;
10 use crate::DirstateEntry;
11 use crate::DirstateError;
11 use crate::DirstateError;
12 use crate::DirstateParents;
12 use crate::DirstateParents;
13 use bitflags::bitflags;
13 use bitflags::bitflags;
14 use bytes_cast::unaligned::{U16Be, U32Be};
14 use bytes_cast::unaligned::{U16Be, U32Be};
15 use bytes_cast::BytesCast;
15 use bytes_cast::BytesCast;
16 use format_bytes::format_bytes;
16 use format_bytes::format_bytes;
17 use std::borrow::Cow;
17 use std::borrow::Cow;
18 use std::convert::{TryFrom, TryInto};
18 use std::convert::{TryFrom, TryInto};
19
19
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
21 /// This a redundant sanity check more than an actual "magic number" since
21 /// This a redundant sanity check more than an actual "magic number" since
22 /// `.hg/requires` already governs which format should be used.
22 /// `.hg/requires` already governs which format should be used.
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
24
24
25 /// Keep space for 256-bit hashes
25 /// Keep space for 256-bit hashes
26 const STORED_NODE_ID_BYTES: usize = 32;
26 const STORED_NODE_ID_BYTES: usize = 32;
27
27
28 /// … even though only 160 bits are used for now, with SHA-1
28 /// … even though only 160 bits are used for now, with SHA-1
29 const USED_NODE_ID_BYTES: usize = 20;
29 const USED_NODE_ID_BYTES: usize = 20;
30
30
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33
33
34 /// Must match the constant of the same name in
34 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
35 /// `mercurial/dirstateutils/docket.py`
36 const TREE_METADATA_SIZE: usize = 44;
35 const TREE_METADATA_SIZE: usize = 44;
37
38 const NODE_SIZE: usize = 43;
36 const NODE_SIZE: usize = 43;
39
37
40 /// Make sure that size-affecting changes are made knowingly
38 /// Make sure that size-affecting changes are made knowingly
41 #[allow(unused)]
39 #[allow(unused)]
42 fn static_assert_size_of() {
40 fn static_assert_size_of() {
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
46 }
44 }
47
45
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
49 #[derive(BytesCast)]
47 #[derive(BytesCast)]
50 #[repr(C)]
48 #[repr(C)]
51 struct DocketHeader {
49 struct DocketHeader {
52 marker: [u8; V2_FORMAT_MARKER.len()],
50 marker: [u8; V2_FORMAT_MARKER.len()],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
55
53
56 metadata: TreeMetadata,
54 metadata: TreeMetadata,
57
55
58 /// Counted in bytes
56 /// Counted in bytes
59 data_size: Size,
57 data_size: Size,
60
58
61 uuid_size: u8,
59 uuid_size: u8,
62 }
60 }
63
61
64 pub struct Docket<'on_disk> {
62 pub struct Docket<'on_disk> {
65 header: &'on_disk DocketHeader,
63 header: &'on_disk DocketHeader,
66 uuid: &'on_disk [u8],
64 uuid: &'on_disk [u8],
67 }
65 }
68
66
69 /// Fields are documented in the *Tree metadata in the docket file*
67 /// Fields are documented in the *Tree metadata in the docket file*
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
71 #[derive(BytesCast)]
69 #[derive(BytesCast)]
72 #[repr(C)]
70 #[repr(C)]
73 struct TreeMetadata {
71 struct TreeMetadata {
74 root_nodes: ChildNodes,
72 root_nodes: ChildNodes,
75 nodes_with_entry_count: Size,
73 nodes_with_entry_count: Size,
76 nodes_with_copy_source_count: Size,
74 nodes_with_copy_source_count: Size,
77 unreachable_bytes: Size,
75 unreachable_bytes: Size,
78 unused: [u8; 4],
76 unused: [u8; 4],
79
77
80 /// See *Optional hash of ignore patterns* section of
78 /// See *Optional hash of ignore patterns* section of
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
82 ignore_patterns_hash: IgnorePatternsHash,
80 ignore_patterns_hash: IgnorePatternsHash,
83 }
81 }
84
82
85 /// Fields are documented in the *The data file format*
83 /// Fields are documented in the *The data file format*
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
87 #[derive(BytesCast)]
85 #[derive(BytesCast)]
88 #[repr(C)]
86 #[repr(C)]
89 pub(super) struct Node {
87 pub(super) struct Node {
90 full_path: PathSlice,
88 full_path: PathSlice,
91
89
92 /// In bytes from `self.full_path.start`
90 /// In bytes from `self.full_path.start`
93 base_name_start: PathSize,
91 base_name_start: PathSize,
94
92
95 copy_source: OptPathSlice,
93 copy_source: OptPathSlice,
96 children: ChildNodes,
94 children: ChildNodes,
97 pub(super) descendants_with_entry_count: Size,
95 pub(super) descendants_with_entry_count: Size,
98 pub(super) tracked_descendants_count: Size,
96 pub(super) tracked_descendants_count: Size,
99 flags: Flags,
97 flags: Flags,
100 size: U32Be,
98 size: U32Be,
101 mtime: PackedTruncatedTimestamp,
99 mtime: PackedTruncatedTimestamp,
102 }
100 }
103
101
104 bitflags! {
102 bitflags! {
105 #[derive(BytesCast)]
103 #[derive(BytesCast)]
106 #[repr(C)]
104 #[repr(C)]
107 struct Flags: u8 {
105 struct Flags: u8 {
108 const WDIR_TRACKED = 1 << 0;
106 const WDIR_TRACKED = 1 << 0;
109 const P1_TRACKED = 1 << 1;
107 const P1_TRACKED = 1 << 1;
110 const P2_INFO = 1 << 2;
108 const P2_INFO = 1 << 2;
111 const HAS_MODE_AND_SIZE = 1 << 3;
109 const HAS_MODE_AND_SIZE = 1 << 3;
112 const HAS_MTIME = 1 << 4;
110 const HAS_MTIME = 1 << 4;
113 const MODE_EXEC_PERM = 1 << 5;
111 const MODE_EXEC_PERM = 1 << 5;
114 const MODE_IS_SYMLINK = 1 << 6;
112 const MODE_IS_SYMLINK = 1 << 6;
115 }
113 }
116 }
114 }
117
115
118 /// Duration since the Unix epoch
116 /// Duration since the Unix epoch
119 #[derive(BytesCast, Copy, Clone)]
117 #[derive(BytesCast, Copy, Clone)]
120 #[repr(C)]
118 #[repr(C)]
121 struct PackedTruncatedTimestamp {
119 struct PackedTruncatedTimestamp {
122 truncated_seconds: U32Be,
120 truncated_seconds: U32Be,
123 nanoseconds: U32Be,
121 nanoseconds: U32Be,
124 }
122 }
125
123
126 /// Counted in bytes from the start of the file
124 /// Counted in bytes from the start of the file
127 ///
125 ///
128 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
126 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
129 type Offset = U32Be;
127 type Offset = U32Be;
130
128
131 /// Counted in number of items
129 /// Counted in number of items
132 ///
130 ///
133 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
131 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
134 type Size = U32Be;
132 type Size = U32Be;
135
133
136 /// Counted in bytes
134 /// Counted in bytes
137 ///
135 ///
138 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
136 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
139 type PathSize = U16Be;
137 type PathSize = U16Be;
140
138
141 /// A contiguous sequence of `len` times `Node`, representing the child nodes
139 /// A contiguous sequence of `len` times `Node`, representing the child nodes
142 /// of either some other node or of the repository root.
140 /// of either some other node or of the repository root.
143 ///
141 ///
144 /// Always sorted by ascending `full_path`, to allow binary search.
142 /// Always sorted by ascending `full_path`, to allow binary search.
145 /// Since nodes with the same parent nodes also have the same parent path,
143 /// Since nodes with the same parent nodes also have the same parent path,
146 /// only the `base_name`s need to be compared during binary search.
144 /// only the `base_name`s need to be compared during binary search.
147 #[derive(BytesCast, Copy, Clone)]
145 #[derive(BytesCast, Copy, Clone)]
148 #[repr(C)]
146 #[repr(C)]
149 struct ChildNodes {
147 struct ChildNodes {
150 start: Offset,
148 start: Offset,
151 len: Size,
149 len: Size,
152 }
150 }
153
151
154 /// A `HgPath` of `len` bytes
152 /// A `HgPath` of `len` bytes
155 #[derive(BytesCast, Copy, Clone)]
153 #[derive(BytesCast, Copy, Clone)]
156 #[repr(C)]
154 #[repr(C)]
157 struct PathSlice {
155 struct PathSlice {
158 start: Offset,
156 start: Offset,
159 len: PathSize,
157 len: PathSize,
160 }
158 }
161
159
162 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
160 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
163 type OptPathSlice = PathSlice;
161 type OptPathSlice = PathSlice;
164
162
165 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
163 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
166 ///
164 ///
167 /// This should only happen if Mercurial is buggy or a repository is corrupted.
165 /// This should only happen if Mercurial is buggy or a repository is corrupted.
168 #[derive(Debug)]
166 #[derive(Debug)]
169 pub struct DirstateV2ParseError;
167 pub struct DirstateV2ParseError;
170
168
171 impl From<DirstateV2ParseError> for HgError {
169 impl From<DirstateV2ParseError> for HgError {
172 fn from(_: DirstateV2ParseError) -> Self {
170 fn from(_: DirstateV2ParseError) -> Self {
173 HgError::corrupted("dirstate-v2 parse error")
171 HgError::corrupted("dirstate-v2 parse error")
174 }
172 }
175 }
173 }
176
174
177 impl From<DirstateV2ParseError> for crate::DirstateError {
175 impl From<DirstateV2ParseError> for crate::DirstateError {
178 fn from(error: DirstateV2ParseError) -> Self {
176 fn from(error: DirstateV2ParseError) -> Self {
179 HgError::from(error).into()
177 HgError::from(error).into()
180 }
178 }
181 }
179 }
182
180
183 impl<'on_disk> Docket<'on_disk> {
181 impl<'on_disk> Docket<'on_disk> {
184 pub fn parents(&self) -> DirstateParents {
182 pub fn parents(&self) -> DirstateParents {
185 use crate::Node;
183 use crate::Node;
186 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
184 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
187 .unwrap()
185 .unwrap()
188 .clone();
186 .clone();
189 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
187 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
190 .unwrap()
188 .unwrap()
191 .clone();
189 .clone();
192 DirstateParents { p1, p2 }
190 DirstateParents { p1, p2 }
193 }
191 }
194
192
195 pub fn tree_metadata(&self) -> &[u8] {
193 pub fn tree_metadata(&self) -> &[u8] {
196 self.header.metadata.as_bytes()
194 self.header.metadata.as_bytes()
197 }
195 }
198
196
199 pub fn data_size(&self) -> usize {
197 pub fn data_size(&self) -> usize {
200 // This `unwrap` could only panic on a 16-bit CPU
198 // This `unwrap` could only panic on a 16-bit CPU
201 self.header.data_size.get().try_into().unwrap()
199 self.header.data_size.get().try_into().unwrap()
202 }
200 }
203
201
204 pub fn data_filename(&self) -> String {
202 pub fn data_filename(&self) -> String {
205 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
203 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
206 }
204 }
207 }
205 }
208
206
209 pub fn read_docket(
207 pub fn read_docket(
210 on_disk: &[u8],
208 on_disk: &[u8],
211 ) -> Result<Docket<'_>, DirstateV2ParseError> {
209 ) -> Result<Docket<'_>, DirstateV2ParseError> {
212 let (header, uuid) =
210 let (header, uuid) =
213 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
211 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
214 let uuid_size = header.uuid_size as usize;
212 let uuid_size = header.uuid_size as usize;
215 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
213 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
216 Ok(Docket { header, uuid })
214 Ok(Docket { header, uuid })
217 } else {
215 } else {
218 Err(DirstateV2ParseError)
216 Err(DirstateV2ParseError)
219 }
217 }
220 }
218 }
221
219
222 pub(super) fn read<'on_disk>(
220 pub(super) fn read<'on_disk>(
223 on_disk: &'on_disk [u8],
221 on_disk: &'on_disk [u8],
224 metadata: &[u8],
222 metadata: &[u8],
225 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
223 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
226 if on_disk.is_empty() {
224 if on_disk.is_empty() {
227 return Ok(DirstateMap::empty(on_disk));
225 return Ok(DirstateMap::empty(on_disk));
228 }
226 }
229 let (meta, _) = TreeMetadata::from_bytes(metadata)
227 let (meta, _) = TreeMetadata::from_bytes(metadata)
230 .map_err(|_| DirstateV2ParseError)?;
228 .map_err(|_| DirstateV2ParseError)?;
231 let dirstate_map = DirstateMap {
229 let dirstate_map = DirstateMap {
232 on_disk,
230 on_disk,
233 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
231 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
234 on_disk,
232 on_disk,
235 meta.root_nodes,
233 meta.root_nodes,
236 )?),
234 )?),
237 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
235 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
238 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
236 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
239 ignore_patterns_hash: meta.ignore_patterns_hash,
237 ignore_patterns_hash: meta.ignore_patterns_hash,
240 unreachable_bytes: meta.unreachable_bytes.get(),
238 unreachable_bytes: meta.unreachable_bytes.get(),
241 };
239 };
242 Ok(dirstate_map)
240 Ok(dirstate_map)
243 }
241 }
244
242
245 impl Node {
243 impl Node {
246 pub(super) fn full_path<'on_disk>(
244 pub(super) fn full_path<'on_disk>(
247 &self,
245 &self,
248 on_disk: &'on_disk [u8],
246 on_disk: &'on_disk [u8],
249 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
247 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
250 read_hg_path(on_disk, self.full_path)
248 read_hg_path(on_disk, self.full_path)
251 }
249 }
252
250
253 pub(super) fn base_name_start<'on_disk>(
251 pub(super) fn base_name_start<'on_disk>(
254 &self,
252 &self,
255 ) -> Result<usize, DirstateV2ParseError> {
253 ) -> Result<usize, DirstateV2ParseError> {
256 let start = self.base_name_start.get();
254 let start = self.base_name_start.get();
257 if start < self.full_path.len.get() {
255 if start < self.full_path.len.get() {
258 let start = usize::try_from(start)
256 let start = usize::try_from(start)
259 // u32 -> usize, could only panic on a 16-bit CPU
257 // u32 -> usize, could only panic on a 16-bit CPU
260 .expect("dirstate-v2 base_name_start out of bounds");
258 .expect("dirstate-v2 base_name_start out of bounds");
261 Ok(start)
259 Ok(start)
262 } else {
260 } else {
263 Err(DirstateV2ParseError)
261 Err(DirstateV2ParseError)
264 }
262 }
265 }
263 }
266
264
267 pub(super) fn base_name<'on_disk>(
265 pub(super) fn base_name<'on_disk>(
268 &self,
266 &self,
269 on_disk: &'on_disk [u8],
267 on_disk: &'on_disk [u8],
270 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
268 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
271 let full_path = self.full_path(on_disk)?;
269 let full_path = self.full_path(on_disk)?;
272 let base_name_start = self.base_name_start()?;
270 let base_name_start = self.base_name_start()?;
273 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
271 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
274 }
272 }
275
273
276 pub(super) fn path<'on_disk>(
274 pub(super) fn path<'on_disk>(
277 &self,
275 &self,
278 on_disk: &'on_disk [u8],
276 on_disk: &'on_disk [u8],
279 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
277 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
280 Ok(WithBasename::from_raw_parts(
278 Ok(WithBasename::from_raw_parts(
281 Cow::Borrowed(self.full_path(on_disk)?),
279 Cow::Borrowed(self.full_path(on_disk)?),
282 self.base_name_start()?,
280 self.base_name_start()?,
283 ))
281 ))
284 }
282 }
285
283
286 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
284 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
287 self.copy_source.start.get() != 0
285 self.copy_source.start.get() != 0
288 }
286 }
289
287
290 pub(super) fn copy_source<'on_disk>(
288 pub(super) fn copy_source<'on_disk>(
291 &self,
289 &self,
292 on_disk: &'on_disk [u8],
290 on_disk: &'on_disk [u8],
293 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
291 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
294 Ok(if self.has_copy_source() {
292 Ok(if self.has_copy_source() {
295 Some(read_hg_path(on_disk, self.copy_source)?)
293 Some(read_hg_path(on_disk, self.copy_source)?)
296 } else {
294 } else {
297 None
295 None
298 })
296 })
299 }
297 }
300
298
301 fn has_entry(&self) -> bool {
299 fn has_entry(&self) -> bool {
302 self.flags.intersects(
300 self.flags.intersects(
303 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
301 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
304 )
302 )
305 }
303 }
306
304
307 pub(super) fn node_data(
305 pub(super) fn node_data(
308 &self,
306 &self,
309 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
307 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
310 if self.has_entry() {
308 if self.has_entry() {
311 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
309 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
312 } else if let Some(mtime) = self.cached_directory_mtime()? {
310 } else if let Some(mtime) = self.cached_directory_mtime()? {
313 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
311 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
314 } else {
312 } else {
315 Ok(dirstate_map::NodeData::None)
313 Ok(dirstate_map::NodeData::None)
316 }
314 }
317 }
315 }
318
316
319 pub(super) fn cached_directory_mtime(
317 pub(super) fn cached_directory_mtime(
320 &self,
318 &self,
321 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
319 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
322 Ok(
320 Ok(
323 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
321 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
324 Some(self.mtime.try_into()?)
322 Some(self.mtime.try_into()?)
325 } else {
323 } else {
326 None
324 None
327 },
325 },
328 )
326 )
329 }
327 }
330
328
331 fn synthesize_unix_mode(&self) -> u32 {
329 fn synthesize_unix_mode(&self) -> u32 {
332 let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) {
330 let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) {
333 libc::S_IFLNK
331 libc::S_IFLNK
334 } else {
332 } else {
335 libc::S_IFREG
333 libc::S_IFREG
336 };
334 };
337 let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) {
335 let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) {
338 0o755
336 0o755
339 } else {
337 } else {
340 0o644
338 0o644
341 };
339 };
342 file_type | permisions
340 file_type | permisions
343 }
341 }
344
342
345 fn assume_entry(&self) -> DirstateEntry {
343 fn assume_entry(&self) -> DirstateEntry {
346 // TODO: convert through raw bits instead?
344 // TODO: convert through raw bits instead?
347 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
345 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
348 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
346 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
349 let p2_info = self.flags.contains(Flags::P2_INFO);
347 let p2_info = self.flags.contains(Flags::P2_INFO);
350 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
348 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
351 Some((self.synthesize_unix_mode(), self.size.into()))
349 Some((self.synthesize_unix_mode(), self.size.into()))
352 } else {
350 } else {
353 None
351 None
354 };
352 };
355 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
353 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
356 Some(self.mtime.truncated_seconds.into())
354 Some(self.mtime.truncated_seconds.into())
357 } else {
355 } else {
358 None
356 None
359 };
357 };
360 DirstateEntry::from_v2_data(
358 DirstateEntry::from_v2_data(
361 wdir_tracked,
359 wdir_tracked,
362 p1_tracked,
360 p1_tracked,
363 p2_info,
361 p2_info,
364 mode_size,
362 mode_size,
365 mtime,
363 mtime,
366 )
364 )
367 }
365 }
368
366
369 pub(super) fn entry(
367 pub(super) fn entry(
370 &self,
368 &self,
371 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
369 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
372 if self.has_entry() {
370 if self.has_entry() {
373 Ok(Some(self.assume_entry()))
371 Ok(Some(self.assume_entry()))
374 } else {
372 } else {
375 Ok(None)
373 Ok(None)
376 }
374 }
377 }
375 }
378
376
379 pub(super) fn children<'on_disk>(
377 pub(super) fn children<'on_disk>(
380 &self,
378 &self,
381 on_disk: &'on_disk [u8],
379 on_disk: &'on_disk [u8],
382 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
380 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
383 read_nodes(on_disk, self.children)
381 read_nodes(on_disk, self.children)
384 }
382 }
385
383
386 pub(super) fn to_in_memory_node<'on_disk>(
384 pub(super) fn to_in_memory_node<'on_disk>(
387 &self,
385 &self,
388 on_disk: &'on_disk [u8],
386 on_disk: &'on_disk [u8],
389 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
387 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
390 Ok(dirstate_map::Node {
388 Ok(dirstate_map::Node {
391 children: dirstate_map::ChildNodes::OnDisk(
389 children: dirstate_map::ChildNodes::OnDisk(
392 self.children(on_disk)?,
390 self.children(on_disk)?,
393 ),
391 ),
394 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
392 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
395 data: self.node_data()?,
393 data: self.node_data()?,
396 descendants_with_entry_count: self
394 descendants_with_entry_count: self
397 .descendants_with_entry_count
395 .descendants_with_entry_count
398 .get(),
396 .get(),
399 tracked_descendants_count: self.tracked_descendants_count.get(),
397 tracked_descendants_count: self.tracked_descendants_count.get(),
400 })
398 })
401 }
399 }
402
400
403 fn from_dirstate_entry(
401 fn from_dirstate_entry(
404 entry: &DirstateEntry,
402 entry: &DirstateEntry,
405 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
403 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
406 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
404 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
407 entry.v2_data();
405 entry.v2_data();
408 // TODO: convert throug raw flag bits instead?
406 // TODO: convert throug raw flag bits instead?
409 let mut flags = Flags::empty();
407 let mut flags = Flags::empty();
410 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
408 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
411 flags.set(Flags::P1_TRACKED, p1_tracked);
409 flags.set(Flags::P1_TRACKED, p1_tracked);
412 flags.set(Flags::P2_INFO, p2_info);
410 flags.set(Flags::P2_INFO, p2_info);
413 let size = if let Some((m, s)) = mode_size_opt {
411 let size = if let Some((m, s)) = mode_size_opt {
414 let exec_perm = m & libc::S_IXUSR != 0;
412 let exec_perm = m & libc::S_IXUSR != 0;
415 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
413 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
416 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
414 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
417 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
415 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
418 flags.insert(Flags::HAS_MODE_AND_SIZE);
416 flags.insert(Flags::HAS_MODE_AND_SIZE);
419 s.into()
417 s.into()
420 } else {
418 } else {
421 0.into()
419 0.into()
422 };
420 };
423 let mtime = if let Some(m) = mtime_opt {
421 let mtime = if let Some(m) = mtime_opt {
424 flags.insert(Flags::HAS_MTIME);
422 flags.insert(Flags::HAS_MTIME);
425 PackedTruncatedTimestamp {
423 PackedTruncatedTimestamp {
426 truncated_seconds: m.into(),
424 truncated_seconds: m.into(),
427 nanoseconds: 0.into(),
425 nanoseconds: 0.into(),
428 }
426 }
429 } else {
427 } else {
430 PackedTruncatedTimestamp::null()
428 PackedTruncatedTimestamp::null()
431 };
429 };
432 (flags, size, mtime)
430 (flags, size, mtime)
433 }
431 }
434 }
432 }
435
433
436 fn read_hg_path(
434 fn read_hg_path(
437 on_disk: &[u8],
435 on_disk: &[u8],
438 slice: PathSlice,
436 slice: PathSlice,
439 ) -> Result<&HgPath, DirstateV2ParseError> {
437 ) -> Result<&HgPath, DirstateV2ParseError> {
440 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
438 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
441 }
439 }
442
440
443 fn read_nodes(
441 fn read_nodes(
444 on_disk: &[u8],
442 on_disk: &[u8],
445 slice: ChildNodes,
443 slice: ChildNodes,
446 ) -> Result<&[Node], DirstateV2ParseError> {
444 ) -> Result<&[Node], DirstateV2ParseError> {
447 read_slice(on_disk, slice.start, slice.len.get())
445 read_slice(on_disk, slice.start, slice.len.get())
448 }
446 }
449
447
450 fn read_slice<T, Len>(
448 fn read_slice<T, Len>(
451 on_disk: &[u8],
449 on_disk: &[u8],
452 start: Offset,
450 start: Offset,
453 len: Len,
451 len: Len,
454 ) -> Result<&[T], DirstateV2ParseError>
452 ) -> Result<&[T], DirstateV2ParseError>
455 where
453 where
456 T: BytesCast,
454 T: BytesCast,
457 Len: TryInto<usize>,
455 Len: TryInto<usize>,
458 {
456 {
459 // Either `usize::MAX` would result in "out of bounds" error since a single
457 // Either `usize::MAX` would result in "out of bounds" error since a single
460 // `&[u8]` cannot occupy the entire addess space.
458 // `&[u8]` cannot occupy the entire addess space.
461 let start = start.get().try_into().unwrap_or(std::usize::MAX);
459 let start = start.get().try_into().unwrap_or(std::usize::MAX);
462 let len = len.try_into().unwrap_or(std::usize::MAX);
460 let len = len.try_into().unwrap_or(std::usize::MAX);
463 on_disk
461 on_disk
464 .get(start..)
462 .get(start..)
465 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
463 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
466 .map(|(slice, _rest)| slice)
464 .map(|(slice, _rest)| slice)
467 .ok_or_else(|| DirstateV2ParseError)
465 .ok_or_else(|| DirstateV2ParseError)
468 }
466 }
469
467
470 pub(crate) fn for_each_tracked_path<'on_disk>(
468 pub(crate) fn for_each_tracked_path<'on_disk>(
471 on_disk: &'on_disk [u8],
469 on_disk: &'on_disk [u8],
472 metadata: &[u8],
470 metadata: &[u8],
473 mut f: impl FnMut(&'on_disk HgPath),
471 mut f: impl FnMut(&'on_disk HgPath),
474 ) -> Result<(), DirstateV2ParseError> {
472 ) -> Result<(), DirstateV2ParseError> {
475 let (meta, _) = TreeMetadata::from_bytes(metadata)
473 let (meta, _) = TreeMetadata::from_bytes(metadata)
476 .map_err(|_| DirstateV2ParseError)?;
474 .map_err(|_| DirstateV2ParseError)?;
477 fn recur<'on_disk>(
475 fn recur<'on_disk>(
478 on_disk: &'on_disk [u8],
476 on_disk: &'on_disk [u8],
479 nodes: ChildNodes,
477 nodes: ChildNodes,
480 f: &mut impl FnMut(&'on_disk HgPath),
478 f: &mut impl FnMut(&'on_disk HgPath),
481 ) -> Result<(), DirstateV2ParseError> {
479 ) -> Result<(), DirstateV2ParseError> {
482 for node in read_nodes(on_disk, nodes)? {
480 for node in read_nodes(on_disk, nodes)? {
483 if let Some(entry) = node.entry()? {
481 if let Some(entry) = node.entry()? {
484 if entry.state().is_tracked() {
482 if entry.state().is_tracked() {
485 f(node.full_path(on_disk)?)
483 f(node.full_path(on_disk)?)
486 }
484 }
487 }
485 }
488 recur(on_disk, node.children, f)?
486 recur(on_disk, node.children, f)?
489 }
487 }
490 Ok(())
488 Ok(())
491 }
489 }
492 recur(on_disk, meta.root_nodes, &mut f)
490 recur(on_disk, meta.root_nodes, &mut f)
493 }
491 }
494
492
495 /// Returns new data and metadata, together with whether that data should be
493 /// Returns new data and metadata, together with whether that data should be
496 /// appended to the existing data file whose content is at
494 /// appended to the existing data file whose content is at
497 /// `dirstate_map.on_disk` (true), instead of written to a new data file
495 /// `dirstate_map.on_disk` (true), instead of written to a new data file
498 /// (false).
496 /// (false).
499 pub(super) fn write(
497 pub(super) fn write(
500 dirstate_map: &mut DirstateMap,
498 dirstate_map: &mut DirstateMap,
501 can_append: bool,
499 can_append: bool,
502 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
500 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
503 let append = can_append && dirstate_map.write_should_append();
501 let append = can_append && dirstate_map.write_should_append();
504
502
505 // This ignores the space for paths, and for nodes without an entry.
503 // This ignores the space for paths, and for nodes without an entry.
506 // TODO: better estimate? Skip the `Vec` and write to a file directly?
504 // TODO: better estimate? Skip the `Vec` and write to a file directly?
507 let size_guess = std::mem::size_of::<Node>()
505 let size_guess = std::mem::size_of::<Node>()
508 * dirstate_map.nodes_with_entry_count as usize;
506 * dirstate_map.nodes_with_entry_count as usize;
509
507
510 let mut writer = Writer {
508 let mut writer = Writer {
511 dirstate_map,
509 dirstate_map,
512 append,
510 append,
513 out: Vec::with_capacity(size_guess),
511 out: Vec::with_capacity(size_guess),
514 };
512 };
515
513
516 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
514 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
517
515
518 let meta = TreeMetadata {
516 let meta = TreeMetadata {
519 root_nodes,
517 root_nodes,
520 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
518 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
521 nodes_with_copy_source_count: dirstate_map
519 nodes_with_copy_source_count: dirstate_map
522 .nodes_with_copy_source_count
520 .nodes_with_copy_source_count
523 .into(),
521 .into(),
524 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
522 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
525 unused: [0; 4],
523 unused: [0; 4],
526 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
524 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
527 };
525 };
528 Ok((writer.out, meta.as_bytes().to_vec(), append))
526 Ok((writer.out, meta.as_bytes().to_vec(), append))
529 }
527 }
530
528
531 struct Writer<'dmap, 'on_disk> {
529 struct Writer<'dmap, 'on_disk> {
532 dirstate_map: &'dmap DirstateMap<'on_disk>,
530 dirstate_map: &'dmap DirstateMap<'on_disk>,
533 append: bool,
531 append: bool,
534 out: Vec<u8>,
532 out: Vec<u8>,
535 }
533 }
536
534
537 impl Writer<'_, '_> {
535 impl Writer<'_, '_> {
538 fn write_nodes(
536 fn write_nodes(
539 &mut self,
537 &mut self,
540 nodes: dirstate_map::ChildNodesRef,
538 nodes: dirstate_map::ChildNodesRef,
541 ) -> Result<ChildNodes, DirstateError> {
539 ) -> Result<ChildNodes, DirstateError> {
542 // Reuse already-written nodes if possible
540 // Reuse already-written nodes if possible
543 if self.append {
541 if self.append {
544 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
542 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
545 let start = self.on_disk_offset_of(nodes_slice).expect(
543 let start = self.on_disk_offset_of(nodes_slice).expect(
546 "dirstate-v2 OnDisk nodes not found within on_disk",
544 "dirstate-v2 OnDisk nodes not found within on_disk",
547 );
545 );
548 let len = child_nodes_len_from_usize(nodes_slice.len());
546 let len = child_nodes_len_from_usize(nodes_slice.len());
549 return Ok(ChildNodes { start, len });
547 return Ok(ChildNodes { start, len });
550 }
548 }
551 }
549 }
552
550
553 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
551 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
554 // undefined iteration order. Sort to enable binary search in the
552 // undefined iteration order. Sort to enable binary search in the
555 // written file.
553 // written file.
556 let nodes = nodes.sorted();
554 let nodes = nodes.sorted();
557 let nodes_len = nodes.len();
555 let nodes_len = nodes.len();
558
556
559 // First accumulate serialized nodes in a `Vec`
557 // First accumulate serialized nodes in a `Vec`
560 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
558 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
561 for node in nodes {
559 for node in nodes {
562 let children =
560 let children =
563 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
561 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
564 let full_path = node.full_path(self.dirstate_map.on_disk)?;
562 let full_path = node.full_path(self.dirstate_map.on_disk)?;
565 let full_path = self.write_path(full_path.as_bytes());
563 let full_path = self.write_path(full_path.as_bytes());
566 let copy_source = if let Some(source) =
564 let copy_source = if let Some(source) =
567 node.copy_source(self.dirstate_map.on_disk)?
565 node.copy_source(self.dirstate_map.on_disk)?
568 {
566 {
569 self.write_path(source.as_bytes())
567 self.write_path(source.as_bytes())
570 } else {
568 } else {
571 PathSlice {
569 PathSlice {
572 start: 0.into(),
570 start: 0.into(),
573 len: 0.into(),
571 len: 0.into(),
574 }
572 }
575 };
573 };
576 on_disk_nodes.push(match node {
574 on_disk_nodes.push(match node {
577 NodeRef::InMemory(path, node) => {
575 NodeRef::InMemory(path, node) => {
578 let (flags, size, mtime) = match &node.data {
576 let (flags, size, mtime) = match &node.data {
579 dirstate_map::NodeData::Entry(entry) => {
577 dirstate_map::NodeData::Entry(entry) => {
580 Node::from_dirstate_entry(entry)
578 Node::from_dirstate_entry(entry)
581 }
579 }
582 dirstate_map::NodeData::CachedDirectory { mtime } => {
580 dirstate_map::NodeData::CachedDirectory { mtime } => {
583 (Flags::HAS_MTIME, 0.into(), (*mtime).into())
581 (Flags::HAS_MTIME, 0.into(), (*mtime).into())
584 }
582 }
585 dirstate_map::NodeData::None => (
583 dirstate_map::NodeData::None => (
586 Flags::empty(),
584 Flags::empty(),
587 0.into(),
585 0.into(),
588 PackedTruncatedTimestamp::null(),
586 PackedTruncatedTimestamp::null(),
589 ),
587 ),
590 };
588 };
591 Node {
589 Node {
592 children,
590 children,
593 copy_source,
591 copy_source,
594 full_path,
592 full_path,
595 base_name_start: u16::try_from(path.base_name_start())
593 base_name_start: u16::try_from(path.base_name_start())
596 // Could only panic for paths over 64 KiB
594 // Could only panic for paths over 64 KiB
597 .expect("dirstate-v2 path length overflow")
595 .expect("dirstate-v2 path length overflow")
598 .into(),
596 .into(),
599 descendants_with_entry_count: node
597 descendants_with_entry_count: node
600 .descendants_with_entry_count
598 .descendants_with_entry_count
601 .into(),
599 .into(),
602 tracked_descendants_count: node
600 tracked_descendants_count: node
603 .tracked_descendants_count
601 .tracked_descendants_count
604 .into(),
602 .into(),
605 flags,
603 flags,
606 size,
604 size,
607 mtime,
605 mtime,
608 }
606 }
609 }
607 }
610 NodeRef::OnDisk(node) => Node {
608 NodeRef::OnDisk(node) => Node {
611 children,
609 children,
612 copy_source,
610 copy_source,
613 full_path,
611 full_path,
614 ..*node
612 ..*node
615 },
613 },
616 })
614 })
617 }
615 }
618 // … so we can write them contiguously, after writing everything else
616 // … so we can write them contiguously, after writing everything else
619 // they refer to.
617 // they refer to.
620 let start = self.current_offset();
618 let start = self.current_offset();
621 let len = child_nodes_len_from_usize(nodes_len);
619 let len = child_nodes_len_from_usize(nodes_len);
622 self.out.extend(on_disk_nodes.as_bytes());
620 self.out.extend(on_disk_nodes.as_bytes());
623 Ok(ChildNodes { start, len })
621 Ok(ChildNodes { start, len })
624 }
622 }
625
623
626 /// If the given slice of items is within `on_disk`, returns its offset
624 /// If the given slice of items is within `on_disk`, returns its offset
627 /// from the start of `on_disk`.
625 /// from the start of `on_disk`.
628 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
626 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
629 where
627 where
630 T: BytesCast,
628 T: BytesCast,
631 {
629 {
632 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
630 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
633 let start = slice.as_ptr() as usize;
631 let start = slice.as_ptr() as usize;
634 let end = start + slice.len();
632 let end = start + slice.len();
635 start..=end
633 start..=end
636 }
634 }
637 let slice_addresses = address_range(slice.as_bytes());
635 let slice_addresses = address_range(slice.as_bytes());
638 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
636 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
639 if on_disk_addresses.contains(slice_addresses.start())
637 if on_disk_addresses.contains(slice_addresses.start())
640 && on_disk_addresses.contains(slice_addresses.end())
638 && on_disk_addresses.contains(slice_addresses.end())
641 {
639 {
642 let offset = slice_addresses.start() - on_disk_addresses.start();
640 let offset = slice_addresses.start() - on_disk_addresses.start();
643 Some(offset_from_usize(offset))
641 Some(offset_from_usize(offset))
644 } else {
642 } else {
645 None
643 None
646 }
644 }
647 }
645 }
648
646
649 fn current_offset(&mut self) -> Offset {
647 fn current_offset(&mut self) -> Offset {
650 let mut offset = self.out.len();
648 let mut offset = self.out.len();
651 if self.append {
649 if self.append {
652 offset += self.dirstate_map.on_disk.len()
650 offset += self.dirstate_map.on_disk.len()
653 }
651 }
654 offset_from_usize(offset)
652 offset_from_usize(offset)
655 }
653 }
656
654
657 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
655 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
658 let len = path_len_from_usize(slice.len());
656 let len = path_len_from_usize(slice.len());
659 // Reuse an already-written path if possible
657 // Reuse an already-written path if possible
660 if self.append {
658 if self.append {
661 if let Some(start) = self.on_disk_offset_of(slice) {
659 if let Some(start) = self.on_disk_offset_of(slice) {
662 return PathSlice { start, len };
660 return PathSlice { start, len };
663 }
661 }
664 }
662 }
665 let start = self.current_offset();
663 let start = self.current_offset();
666 self.out.extend(slice.as_bytes());
664 self.out.extend(slice.as_bytes());
667 PathSlice { start, len }
665 PathSlice { start, len }
668 }
666 }
669 }
667 }
670
668
671 fn offset_from_usize(x: usize) -> Offset {
669 fn offset_from_usize(x: usize) -> Offset {
672 u32::try_from(x)
670 u32::try_from(x)
673 // Could only panic for a dirstate file larger than 4 GiB
671 // Could only panic for a dirstate file larger than 4 GiB
674 .expect("dirstate-v2 offset overflow")
672 .expect("dirstate-v2 offset overflow")
675 .into()
673 .into()
676 }
674 }
677
675
678 fn child_nodes_len_from_usize(x: usize) -> Size {
676 fn child_nodes_len_from_usize(x: usize) -> Size {
679 u32::try_from(x)
677 u32::try_from(x)
680 // Could only panic with over 4 billion nodes
678 // Could only panic with over 4 billion nodes
681 .expect("dirstate-v2 slice length overflow")
679 .expect("dirstate-v2 slice length overflow")
682 .into()
680 .into()
683 }
681 }
684
682
685 fn path_len_from_usize(x: usize) -> PathSize {
683 fn path_len_from_usize(x: usize) -> PathSize {
686 u16::try_from(x)
684 u16::try_from(x)
687 // Could only panic for paths over 64 KiB
685 // Could only panic for paths over 64 KiB
688 .expect("dirstate-v2 path length overflow")
686 .expect("dirstate-v2 path length overflow")
689 .into()
687 .into()
690 }
688 }
691
689
692 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
690 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
693 fn from(timestamp: TruncatedTimestamp) -> Self {
691 fn from(timestamp: TruncatedTimestamp) -> Self {
694 Self {
692 Self {
695 truncated_seconds: timestamp.truncated_seconds().into(),
693 truncated_seconds: timestamp.truncated_seconds().into(),
696 nanoseconds: timestamp.nanoseconds().into(),
694 nanoseconds: timestamp.nanoseconds().into(),
697 }
695 }
698 }
696 }
699 }
697 }
700
698
701 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
699 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
702 type Error = DirstateV2ParseError;
700 type Error = DirstateV2ParseError;
703
701
704 fn try_from(
702 fn try_from(
705 timestamp: PackedTruncatedTimestamp,
703 timestamp: PackedTruncatedTimestamp,
706 ) -> Result<Self, Self::Error> {
704 ) -> Result<Self, Self::Error> {
707 Self::from_already_truncated(
705 Self::from_already_truncated(
708 timestamp.truncated_seconds.get(),
706 timestamp.truncated_seconds.get(),
709 timestamp.nanoseconds.get(),
707 timestamp.nanoseconds.get(),
710 )
708 )
711 }
709 }
712 }
710 }
713 impl PackedTruncatedTimestamp {
711 impl PackedTruncatedTimestamp {
714 fn null() -> Self {
712 fn null() -> Self {
715 Self {
713 Self {
716 truncated_seconds: 0.into(),
714 truncated_seconds: 0.into(),
717 nanoseconds: 0.into(),
715 nanoseconds: 0.into(),
718 }
716 }
719 }
717 }
720 }
718 }
General Comments 0
You need to be logged in to leave comments. Login now