##// END OF EJS Templates
dirstate-item: add missing bit of docstring...
Raphaël Gomès -
r50010:4c75f00b default
parent child Browse files
Show More
@@ -1,974 +1,974 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
8
9 import io
9 import io
10 import stat
10 import stat
11 import struct
11 import struct
12 import zlib
12 import zlib
13
13
14 from ..node import (
14 from ..node import (
15 nullrev,
15 nullrev,
16 sha1nodeconstants,
16 sha1nodeconstants,
17 )
17 )
18 from ..thirdparty import attr
18 from ..thirdparty import attr
19 from .. import (
19 from .. import (
20 error,
20 error,
21 revlogutils,
21 revlogutils,
22 util,
22 util,
23 )
23 )
24
24
25 from ..revlogutils import nodemap as nodemaputil
25 from ..revlogutils import nodemap as nodemaputil
26 from ..revlogutils import constants as revlog_constants
26 from ..revlogutils import constants as revlog_constants
27
27
28 stringio = io.BytesIO
28 stringio = io.BytesIO
29
29
30
30
31 _pack = struct.pack
31 _pack = struct.pack
32 _unpack = struct.unpack
32 _unpack = struct.unpack
33 _compress = zlib.compress
33 _compress = zlib.compress
34 _decompress = zlib.decompress
34 _decompress = zlib.decompress
35
35
36
36
37 # a special value used internally for `size` if the file come from the other parent
37 # a special value used internally for `size` if the file come from the other parent
38 FROM_P2 = -2
38 FROM_P2 = -2
39
39
40 # a special value used internally for `size` if the file is modified/merged/added
40 # a special value used internally for `size` if the file is modified/merged/added
41 NONNORMAL = -1
41 NONNORMAL = -1
42
42
43 # a special value used internally for `time` if the time is ambigeous
43 # a special value used internally for `time` if the time is ambigeous
44 AMBIGUOUS_TIME = -1
44 AMBIGUOUS_TIME = -1
45
45
46 # Bits of the `flags` byte inside a node in the file format
46 # Bits of the `flags` byte inside a node in the file format
47 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
47 DIRSTATE_V2_WDIR_TRACKED = 1 << 0
48 DIRSTATE_V2_P1_TRACKED = 1 << 1
48 DIRSTATE_V2_P1_TRACKED = 1 << 1
49 DIRSTATE_V2_P2_INFO = 1 << 2
49 DIRSTATE_V2_P2_INFO = 1 << 2
50 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 3
50 DIRSTATE_V2_MODE_EXEC_PERM = 1 << 3
51 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 4
51 DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 4
52 DIRSTATE_V2_HAS_FALLBACK_EXEC = 1 << 5
52 DIRSTATE_V2_HAS_FALLBACK_EXEC = 1 << 5
53 DIRSTATE_V2_FALLBACK_EXEC = 1 << 6
53 DIRSTATE_V2_FALLBACK_EXEC = 1 << 6
54 DIRSTATE_V2_HAS_FALLBACK_SYMLINK = 1 << 7
54 DIRSTATE_V2_HAS_FALLBACK_SYMLINK = 1 << 7
55 DIRSTATE_V2_FALLBACK_SYMLINK = 1 << 8
55 DIRSTATE_V2_FALLBACK_SYMLINK = 1 << 8
56 DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 9
56 DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 9
57 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 10
57 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 10
58 DIRSTATE_V2_HAS_MTIME = 1 << 11
58 DIRSTATE_V2_HAS_MTIME = 1 << 11
59 DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS = 1 << 12
59 DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS = 1 << 12
60 DIRSTATE_V2_DIRECTORY = 1 << 13
60 DIRSTATE_V2_DIRECTORY = 1 << 13
61 DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 14
61 DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 14
62 DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 15
62 DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 15
63
63
64
64
65 @attr.s(slots=True, init=False)
65 @attr.s(slots=True, init=False)
66 class DirstateItem:
66 class DirstateItem:
67 """represent a dirstate entry
67 """represent a dirstate entry
68
68
69 It hold multiple attributes
69 It hold multiple attributes
70
70
71 # about file tracking
71 # about file tracking
72 - wc_tracked: is the file tracked by the working copy
72 - wc_tracked: is the file tracked by the working copy
73 - p1_tracked: is the file tracked in working copy first parent
73 - p1_tracked: is the file tracked in working copy first parent
74 - p2_info: the file has been involved in some merge operation. Either
74 - p2_info: the file has been involved in some merge operation. Either
75 because it was actually merged, or because the p2 version was
75 because it was actually merged, or because the p2 version was
76 ahead, or because some rename moved it there. In either case
76 ahead, or because some rename moved it there. In either case
77 `hg status` will want it displayed as modified.
77 `hg status` will want it displayed as modified.
78
78
79 # about the file state expected from p1 manifest:
79 # about the file state expected from p1 manifest:
80 - mode: the file mode in p1
80 - mode: the file mode in p1
81 - size: the file size in p1
81 - size: the file size in p1
82
82
83 These value can be set to None, which mean we don't have a meaningful value
83 These value can be set to None, which mean we don't have a meaningful value
84 to compare with. Either because we don't really care about them as there
84 to compare with. Either because we don't really care about them as there
85 `status` is known without having to look at the disk or because we don't
85 `status` is known without having to look at the disk or because we don't
86 know these right now and a full comparison will be needed to find out if
86 know these right now and a full comparison will be needed to find out if
87 the file is clean.
87 the file is clean.
88
88
89 # about the file state on disk last time we saw it:
89 # about the file state on disk last time we saw it:
90 - mtime: the last known clean mtime for the file.
90 - mtime: the last known clean mtime for the file.
91
91
92 This value can be set to None if no cachable state exist. Either because we
92 This value can be set to None if no cachable state exist. Either because we
93 do not care (see previous section) or because we could not cache something
93 do not care (see previous section) or because we could not cache something
94 yet.
94 yet.
95 """
95 """
96
96
97 _wc_tracked = attr.ib()
97 _wc_tracked = attr.ib()
98 _p1_tracked = attr.ib()
98 _p1_tracked = attr.ib()
99 _p2_info = attr.ib()
99 _p2_info = attr.ib()
100 _mode = attr.ib()
100 _mode = attr.ib()
101 _size = attr.ib()
101 _size = attr.ib()
102 _mtime_s = attr.ib()
102 _mtime_s = attr.ib()
103 _mtime_ns = attr.ib()
103 _mtime_ns = attr.ib()
104 _fallback_exec = attr.ib()
104 _fallback_exec = attr.ib()
105 _fallback_symlink = attr.ib()
105 _fallback_symlink = attr.ib()
106 _mtime_second_ambiguous = attr.ib()
106 _mtime_second_ambiguous = attr.ib()
107
107
108 def __init__(
108 def __init__(
109 self,
109 self,
110 wc_tracked=False,
110 wc_tracked=False,
111 p1_tracked=False,
111 p1_tracked=False,
112 p2_info=False,
112 p2_info=False,
113 has_meaningful_data=True,
113 has_meaningful_data=True,
114 has_meaningful_mtime=True,
114 has_meaningful_mtime=True,
115 parentfiledata=None,
115 parentfiledata=None,
116 fallback_exec=None,
116 fallback_exec=None,
117 fallback_symlink=None,
117 fallback_symlink=None,
118 ):
118 ):
119 self._wc_tracked = wc_tracked
119 self._wc_tracked = wc_tracked
120 self._p1_tracked = p1_tracked
120 self._p1_tracked = p1_tracked
121 self._p2_info = p2_info
121 self._p2_info = p2_info
122
122
123 self._fallback_exec = fallback_exec
123 self._fallback_exec = fallback_exec
124 self._fallback_symlink = fallback_symlink
124 self._fallback_symlink = fallback_symlink
125
125
126 self._mode = None
126 self._mode = None
127 self._size = None
127 self._size = None
128 self._mtime_s = None
128 self._mtime_s = None
129 self._mtime_ns = None
129 self._mtime_ns = None
130 self._mtime_second_ambiguous = False
130 self._mtime_second_ambiguous = False
131 if parentfiledata is None:
131 if parentfiledata is None:
132 has_meaningful_mtime = False
132 has_meaningful_mtime = False
133 has_meaningful_data = False
133 has_meaningful_data = False
134 elif parentfiledata[2] is None:
134 elif parentfiledata[2] is None:
135 has_meaningful_mtime = False
135 has_meaningful_mtime = False
136 if has_meaningful_data:
136 if has_meaningful_data:
137 self._mode = parentfiledata[0]
137 self._mode = parentfiledata[0]
138 self._size = parentfiledata[1]
138 self._size = parentfiledata[1]
139 if has_meaningful_mtime:
139 if has_meaningful_mtime:
140 (
140 (
141 self._mtime_s,
141 self._mtime_s,
142 self._mtime_ns,
142 self._mtime_ns,
143 self._mtime_second_ambiguous,
143 self._mtime_second_ambiguous,
144 ) = parentfiledata[2]
144 ) = parentfiledata[2]
145
145
146 @classmethod
146 @classmethod
147 def from_v2_data(cls, flags, size, mtime_s, mtime_ns):
147 def from_v2_data(cls, flags, size, mtime_s, mtime_ns):
148 """Build a new DirstateItem object from V2 data"""
148 """Build a new DirstateItem object from V2 data"""
149 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
149 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
150 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME)
150 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME)
151 mode = None
151 mode = None
152
152
153 if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED:
153 if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED:
154 # we do not have support for this flag in the code yet,
154 # we do not have support for this flag in the code yet,
155 # force a lookup for this file.
155 # force a lookup for this file.
156 has_mode_size = False
156 has_mode_size = False
157 has_meaningful_mtime = False
157 has_meaningful_mtime = False
158
158
159 fallback_exec = None
159 fallback_exec = None
160 if flags & DIRSTATE_V2_HAS_FALLBACK_EXEC:
160 if flags & DIRSTATE_V2_HAS_FALLBACK_EXEC:
161 fallback_exec = flags & DIRSTATE_V2_FALLBACK_EXEC
161 fallback_exec = flags & DIRSTATE_V2_FALLBACK_EXEC
162
162
163 fallback_symlink = None
163 fallback_symlink = None
164 if flags & DIRSTATE_V2_HAS_FALLBACK_SYMLINK:
164 if flags & DIRSTATE_V2_HAS_FALLBACK_SYMLINK:
165 fallback_symlink = flags & DIRSTATE_V2_FALLBACK_SYMLINK
165 fallback_symlink = flags & DIRSTATE_V2_FALLBACK_SYMLINK
166
166
167 if has_mode_size:
167 if has_mode_size:
168 assert stat.S_IXUSR == 0o100
168 assert stat.S_IXUSR == 0o100
169 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
169 if flags & DIRSTATE_V2_MODE_EXEC_PERM:
170 mode = 0o755
170 mode = 0o755
171 else:
171 else:
172 mode = 0o644
172 mode = 0o644
173 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
173 if flags & DIRSTATE_V2_MODE_IS_SYMLINK:
174 mode |= stat.S_IFLNK
174 mode |= stat.S_IFLNK
175 else:
175 else:
176 mode |= stat.S_IFREG
176 mode |= stat.S_IFREG
177
177
178 second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
178 second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
179 return cls(
179 return cls(
180 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
180 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
181 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
181 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
182 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
182 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
183 has_meaningful_data=has_mode_size,
183 has_meaningful_data=has_mode_size,
184 has_meaningful_mtime=has_meaningful_mtime,
184 has_meaningful_mtime=has_meaningful_mtime,
185 parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)),
185 parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)),
186 fallback_exec=fallback_exec,
186 fallback_exec=fallback_exec,
187 fallback_symlink=fallback_symlink,
187 fallback_symlink=fallback_symlink,
188 )
188 )
189
189
190 @classmethod
190 @classmethod
191 def from_v1_data(cls, state, mode, size, mtime):
191 def from_v1_data(cls, state, mode, size, mtime):
192 """Build a new DirstateItem object from V1 data
192 """Build a new DirstateItem object from V1 data
193
193
194 Since the dirstate-v1 format is frozen, the signature of this function
194 Since the dirstate-v1 format is frozen, the signature of this function
195 is not expected to change, unlike the __init__ one.
195 is not expected to change, unlike the __init__ one.
196 """
196 """
197 if state == b'm':
197 if state == b'm':
198 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
198 return cls(wc_tracked=True, p1_tracked=True, p2_info=True)
199 elif state == b'a':
199 elif state == b'a':
200 return cls(wc_tracked=True)
200 return cls(wc_tracked=True)
201 elif state == b'r':
201 elif state == b'r':
202 if size == NONNORMAL:
202 if size == NONNORMAL:
203 p1_tracked = True
203 p1_tracked = True
204 p2_info = True
204 p2_info = True
205 elif size == FROM_P2:
205 elif size == FROM_P2:
206 p1_tracked = False
206 p1_tracked = False
207 p2_info = True
207 p2_info = True
208 else:
208 else:
209 p1_tracked = True
209 p1_tracked = True
210 p2_info = False
210 p2_info = False
211 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
211 return cls(p1_tracked=p1_tracked, p2_info=p2_info)
212 elif state == b'n':
212 elif state == b'n':
213 if size == FROM_P2:
213 if size == FROM_P2:
214 return cls(wc_tracked=True, p2_info=True)
214 return cls(wc_tracked=True, p2_info=True)
215 elif size == NONNORMAL:
215 elif size == NONNORMAL:
216 return cls(wc_tracked=True, p1_tracked=True)
216 return cls(wc_tracked=True, p1_tracked=True)
217 elif mtime == AMBIGUOUS_TIME:
217 elif mtime == AMBIGUOUS_TIME:
218 return cls(
218 return cls(
219 wc_tracked=True,
219 wc_tracked=True,
220 p1_tracked=True,
220 p1_tracked=True,
221 has_meaningful_mtime=False,
221 has_meaningful_mtime=False,
222 parentfiledata=(mode, size, (42, 0, False)),
222 parentfiledata=(mode, size, (42, 0, False)),
223 )
223 )
224 else:
224 else:
225 return cls(
225 return cls(
226 wc_tracked=True,
226 wc_tracked=True,
227 p1_tracked=True,
227 p1_tracked=True,
228 parentfiledata=(mode, size, (mtime, 0, False)),
228 parentfiledata=(mode, size, (mtime, 0, False)),
229 )
229 )
230 else:
230 else:
231 raise RuntimeError(b'unknown state: %s' % state)
231 raise RuntimeError(b'unknown state: %s' % state)
232
232
233 def set_possibly_dirty(self):
233 def set_possibly_dirty(self):
234 """Mark a file as "possibly dirty"
234 """Mark a file as "possibly dirty"
235
235
236 This means the next status call will have to actually check its content
236 This means the next status call will have to actually check its content
237 to make sure it is correct.
237 to make sure it is correct.
238 """
238 """
239 self._mtime_s = None
239 self._mtime_s = None
240 self._mtime_ns = None
240 self._mtime_ns = None
241
241
242 def set_clean(self, mode, size, mtime):
242 def set_clean(self, mode, size, mtime):
243 """mark a file as "clean" cancelling potential "possibly dirty call"
243 """mark a file as "clean" cancelling potential "possibly dirty call"
244
244
245 Note: this function is a descendant of `dirstate.normal` and is
245 Note: this function is a descendant of `dirstate.normal` and is
246 currently expected to be call on "normal" entry only. There are not
246 currently expected to be call on "normal" entry only. There are not
247 reason for this to not change in the future as long as the ccode is
247 reason for this to not change in the future as long as the ccode is
248 updated to preserve the proper state of the non-normal files.
248 updated to preserve the proper state of the non-normal files.
249 """
249 """
250 self._wc_tracked = True
250 self._wc_tracked = True
251 self._p1_tracked = True
251 self._p1_tracked = True
252 self._mode = mode
252 self._mode = mode
253 self._size = size
253 self._size = size
254 self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime
254 self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime
255
255
256 def set_tracked(self):
256 def set_tracked(self):
257 """mark a file as tracked in the working copy
257 """mark a file as tracked in the working copy
258
258
259 This will ultimately be called by command like `hg add`.
259 This will ultimately be called by command like `hg add`.
260 """
260 """
261 self._wc_tracked = True
261 self._wc_tracked = True
262 # `set_tracked` is replacing various `normallookup` call. So we mark
262 # `set_tracked` is replacing various `normallookup` call. So we mark
263 # the files as needing lookup
263 # the files as needing lookup
264 #
264 #
265 # Consider dropping this in the future in favor of something less broad.
265 # Consider dropping this in the future in favor of something less broad.
266 self._mtime_s = None
266 self._mtime_s = None
267 self._mtime_ns = None
267 self._mtime_ns = None
268
268
269 def set_untracked(self):
269 def set_untracked(self):
270 """mark a file as untracked in the working copy
270 """mark a file as untracked in the working copy
271
271
272 This will ultimately be called by command like `hg remove`.
272 This will ultimately be called by command like `hg remove`.
273 """
273 """
274 self._wc_tracked = False
274 self._wc_tracked = False
275 self._mode = None
275 self._mode = None
276 self._size = None
276 self._size = None
277 self._mtime_s = None
277 self._mtime_s = None
278 self._mtime_ns = None
278 self._mtime_ns = None
279
279
280 def drop_merge_data(self):
280 def drop_merge_data(self):
281 """remove all "merge-only" from a DirstateItem
281 """remove all "merge-only" information from a DirstateItem
282
282
283 This is to be call by the dirstatemap code when the second parent is dropped
283 This is to be call by the dirstatemap code when the second parent is dropped
284 """
284 """
285 if self._p2_info:
285 if self._p2_info:
286 self._p2_info = False
286 self._p2_info = False
287 self._mode = None
287 self._mode = None
288 self._size = None
288 self._size = None
289 self._mtime_s = None
289 self._mtime_s = None
290 self._mtime_ns = None
290 self._mtime_ns = None
291
291
292 @property
292 @property
293 def mode(self):
293 def mode(self):
294 return self._v1_mode()
294 return self._v1_mode()
295
295
296 @property
296 @property
297 def size(self):
297 def size(self):
298 return self._v1_size()
298 return self._v1_size()
299
299
300 @property
300 @property
301 def mtime(self):
301 def mtime(self):
302 return self._v1_mtime()
302 return self._v1_mtime()
303
303
304 def mtime_likely_equal_to(self, other_mtime):
304 def mtime_likely_equal_to(self, other_mtime):
305 self_sec = self._mtime_s
305 self_sec = self._mtime_s
306 if self_sec is None:
306 if self_sec is None:
307 return False
307 return False
308 self_ns = self._mtime_ns
308 self_ns = self._mtime_ns
309 other_sec, other_ns, second_ambiguous = other_mtime
309 other_sec, other_ns, second_ambiguous = other_mtime
310 if self_sec != other_sec:
310 if self_sec != other_sec:
311 # seconds are different theses mtime are definitly not equal
311 # seconds are different theses mtime are definitly not equal
312 return False
312 return False
313 elif other_ns == 0 or self_ns == 0:
313 elif other_ns == 0 or self_ns == 0:
314 # at least one side as no nano-seconds information
314 # at least one side as no nano-seconds information
315
315
316 if self._mtime_second_ambiguous:
316 if self._mtime_second_ambiguous:
317 # We cannot trust the mtime in this case
317 # We cannot trust the mtime in this case
318 return False
318 return False
319 else:
319 else:
320 # the "seconds" value was reliable on its own. We are good to go.
320 # the "seconds" value was reliable on its own. We are good to go.
321 return True
321 return True
322 else:
322 else:
323 # We have nano second information, let us use them !
323 # We have nano second information, let us use them !
324 return self_ns == other_ns
324 return self_ns == other_ns
325
325
326 @property
326 @property
327 def state(self):
327 def state(self):
328 """
328 """
329 States are:
329 States are:
330 n normal
330 n normal
331 m needs merging
331 m needs merging
332 r marked for removal
332 r marked for removal
333 a marked for addition
333 a marked for addition
334
334
335 XXX This "state" is a bit obscure and mostly a direct expression of the
335 XXX This "state" is a bit obscure and mostly a direct expression of the
336 dirstatev1 format. It would make sense to ultimately deprecate it in
336 dirstatev1 format. It would make sense to ultimately deprecate it in
337 favor of the more "semantic" attributes.
337 favor of the more "semantic" attributes.
338 """
338 """
339 if not self.any_tracked:
339 if not self.any_tracked:
340 return b'?'
340 return b'?'
341 return self._v1_state()
341 return self._v1_state()
342
342
343 @property
343 @property
344 def has_fallback_exec(self):
344 def has_fallback_exec(self):
345 """True if "fallback" information are available for the "exec" bit
345 """True if "fallback" information are available for the "exec" bit
346
346
347 Fallback information can be stored in the dirstate to keep track of
347 Fallback information can be stored in the dirstate to keep track of
348 filesystem attribute tracked by Mercurial when the underlying file
348 filesystem attribute tracked by Mercurial when the underlying file
349 system or operating system does not support that property, (e.g.
349 system or operating system does not support that property, (e.g.
350 Windows).
350 Windows).
351
351
352 Not all version of the dirstate on-disk storage support preserving this
352 Not all version of the dirstate on-disk storage support preserving this
353 information.
353 information.
354 """
354 """
355 return self._fallback_exec is not None
355 return self._fallback_exec is not None
356
356
357 @property
357 @property
358 def fallback_exec(self):
358 def fallback_exec(self):
359 """ "fallback" information for the executable bit
359 """ "fallback" information for the executable bit
360
360
361 True if the file should be considered executable when we cannot get
361 True if the file should be considered executable when we cannot get
362 this information from the files system. False if it should be
362 this information from the files system. False if it should be
363 considered non-executable.
363 considered non-executable.
364
364
365 See has_fallback_exec for details."""
365 See has_fallback_exec for details."""
366 return self._fallback_exec
366 return self._fallback_exec
367
367
368 @fallback_exec.setter
368 @fallback_exec.setter
369 def set_fallback_exec(self, value):
369 def set_fallback_exec(self, value):
370 """control "fallback" executable bit
370 """control "fallback" executable bit
371
371
372 Set to:
372 Set to:
373 - True if the file should be considered executable,
373 - True if the file should be considered executable,
374 - False if the file should be considered non-executable,
374 - False if the file should be considered non-executable,
375 - None if we do not have valid fallback data.
375 - None if we do not have valid fallback data.
376
376
377 See has_fallback_exec for details."""
377 See has_fallback_exec for details."""
378 if value is None:
378 if value is None:
379 self._fallback_exec = None
379 self._fallback_exec = None
380 else:
380 else:
381 self._fallback_exec = bool(value)
381 self._fallback_exec = bool(value)
382
382
383 @property
383 @property
384 def has_fallback_symlink(self):
384 def has_fallback_symlink(self):
385 """True if "fallback" information are available for symlink status
385 """True if "fallback" information are available for symlink status
386
386
387 Fallback information can be stored in the dirstate to keep track of
387 Fallback information can be stored in the dirstate to keep track of
388 filesystem attribute tracked by Mercurial when the underlying file
388 filesystem attribute tracked by Mercurial when the underlying file
389 system or operating system does not support that property, (e.g.
389 system or operating system does not support that property, (e.g.
390 Windows).
390 Windows).
391
391
392 Not all version of the dirstate on-disk storage support preserving this
392 Not all version of the dirstate on-disk storage support preserving this
393 information."""
393 information."""
394 return self._fallback_symlink is not None
394 return self._fallback_symlink is not None
395
395
396 @property
396 @property
397 def fallback_symlink(self):
397 def fallback_symlink(self):
398 """ "fallback" information for symlink status
398 """ "fallback" information for symlink status
399
399
400 True if the file should be considered executable when we cannot get
400 True if the file should be considered executable when we cannot get
401 this information from the files system. False if it should be
401 this information from the files system. False if it should be
402 considered non-executable.
402 considered non-executable.
403
403
404 See has_fallback_exec for details."""
404 See has_fallback_exec for details."""
405 return self._fallback_symlink
405 return self._fallback_symlink
406
406
407 @fallback_symlink.setter
407 @fallback_symlink.setter
408 def set_fallback_symlink(self, value):
408 def set_fallback_symlink(self, value):
409 """control "fallback" symlink status
409 """control "fallback" symlink status
410
410
411 Set to:
411 Set to:
412 - True if the file should be considered a symlink,
412 - True if the file should be considered a symlink,
413 - False if the file should be considered not a symlink,
413 - False if the file should be considered not a symlink,
414 - None if we do not have valid fallback data.
414 - None if we do not have valid fallback data.
415
415
416 See has_fallback_symlink for details."""
416 See has_fallback_symlink for details."""
417 if value is None:
417 if value is None:
418 self._fallback_symlink = None
418 self._fallback_symlink = None
419 else:
419 else:
420 self._fallback_symlink = bool(value)
420 self._fallback_symlink = bool(value)
421
421
422 @property
422 @property
423 def tracked(self):
423 def tracked(self):
424 """True is the file is tracked in the working copy"""
424 """True is the file is tracked in the working copy"""
425 return self._wc_tracked
425 return self._wc_tracked
426
426
427 @property
427 @property
428 def any_tracked(self):
428 def any_tracked(self):
429 """True is the file is tracked anywhere (wc or parents)"""
429 """True is the file is tracked anywhere (wc or parents)"""
430 return self._wc_tracked or self._p1_tracked or self._p2_info
430 return self._wc_tracked or self._p1_tracked or self._p2_info
431
431
432 @property
432 @property
433 def added(self):
433 def added(self):
434 """True if the file has been added"""
434 """True if the file has been added"""
435 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
435 return self._wc_tracked and not (self._p1_tracked or self._p2_info)
436
436
437 @property
437 @property
438 def maybe_clean(self):
438 def maybe_clean(self):
439 """True if the file has a chance to be in the "clean" state"""
439 """True if the file has a chance to be in the "clean" state"""
440 if not self._wc_tracked:
440 if not self._wc_tracked:
441 return False
441 return False
442 elif not self._p1_tracked:
442 elif not self._p1_tracked:
443 return False
443 return False
444 elif self._p2_info:
444 elif self._p2_info:
445 return False
445 return False
446 return True
446 return True
447
447
448 @property
448 @property
449 def p1_tracked(self):
449 def p1_tracked(self):
450 """True if the file is tracked in the first parent manifest"""
450 """True if the file is tracked in the first parent manifest"""
451 return self._p1_tracked
451 return self._p1_tracked
452
452
453 @property
453 @property
454 def p2_info(self):
454 def p2_info(self):
455 """True if the file needed to merge or apply any input from p2
455 """True if the file needed to merge or apply any input from p2
456
456
457 See the class documentation for details.
457 See the class documentation for details.
458 """
458 """
459 return self._wc_tracked and self._p2_info
459 return self._wc_tracked and self._p2_info
460
460
461 @property
461 @property
462 def removed(self):
462 def removed(self):
463 """True if the file has been removed"""
463 """True if the file has been removed"""
464 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
464 return not self._wc_tracked and (self._p1_tracked or self._p2_info)
465
465
466 def v2_data(self):
466 def v2_data(self):
467 """Returns (flags, mode, size, mtime) for v2 serialization"""
467 """Returns (flags, mode, size, mtime) for v2 serialization"""
468 flags = 0
468 flags = 0
469 if self._wc_tracked:
469 if self._wc_tracked:
470 flags |= DIRSTATE_V2_WDIR_TRACKED
470 flags |= DIRSTATE_V2_WDIR_TRACKED
471 if self._p1_tracked:
471 if self._p1_tracked:
472 flags |= DIRSTATE_V2_P1_TRACKED
472 flags |= DIRSTATE_V2_P1_TRACKED
473 if self._p2_info:
473 if self._p2_info:
474 flags |= DIRSTATE_V2_P2_INFO
474 flags |= DIRSTATE_V2_P2_INFO
475 if self._mode is not None and self._size is not None:
475 if self._mode is not None and self._size is not None:
476 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
476 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
477 if self.mode & stat.S_IXUSR:
477 if self.mode & stat.S_IXUSR:
478 flags |= DIRSTATE_V2_MODE_EXEC_PERM
478 flags |= DIRSTATE_V2_MODE_EXEC_PERM
479 if stat.S_ISLNK(self.mode):
479 if stat.S_ISLNK(self.mode):
480 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
480 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
481 if self._mtime_s is not None:
481 if self._mtime_s is not None:
482 flags |= DIRSTATE_V2_HAS_MTIME
482 flags |= DIRSTATE_V2_HAS_MTIME
483 if self._mtime_second_ambiguous:
483 if self._mtime_second_ambiguous:
484 flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
484 flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS
485
485
486 if self._fallback_exec is not None:
486 if self._fallback_exec is not None:
487 flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC
487 flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC
488 if self._fallback_exec:
488 if self._fallback_exec:
489 flags |= DIRSTATE_V2_FALLBACK_EXEC
489 flags |= DIRSTATE_V2_FALLBACK_EXEC
490
490
491 if self._fallback_symlink is not None:
491 if self._fallback_symlink is not None:
492 flags |= DIRSTATE_V2_HAS_FALLBACK_SYMLINK
492 flags |= DIRSTATE_V2_HAS_FALLBACK_SYMLINK
493 if self._fallback_symlink:
493 if self._fallback_symlink:
494 flags |= DIRSTATE_V2_FALLBACK_SYMLINK
494 flags |= DIRSTATE_V2_FALLBACK_SYMLINK
495
495
496 # Note: we do not need to do anything regarding
496 # Note: we do not need to do anything regarding
497 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
497 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
498 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
498 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
499 return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0)
499 return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0)
500
500
501 def _v1_state(self):
501 def _v1_state(self):
502 """return a "state" suitable for v1 serialization"""
502 """return a "state" suitable for v1 serialization"""
503 if not self.any_tracked:
503 if not self.any_tracked:
504 # the object has no state to record, this is -currently-
504 # the object has no state to record, this is -currently-
505 # unsupported
505 # unsupported
506 raise RuntimeError('untracked item')
506 raise RuntimeError('untracked item')
507 elif self.removed:
507 elif self.removed:
508 return b'r'
508 return b'r'
509 elif self._p1_tracked and self._p2_info:
509 elif self._p1_tracked and self._p2_info:
510 return b'm'
510 return b'm'
511 elif self.added:
511 elif self.added:
512 return b'a'
512 return b'a'
513 else:
513 else:
514 return b'n'
514 return b'n'
515
515
516 def _v1_mode(self):
516 def _v1_mode(self):
517 """return a "mode" suitable for v1 serialization"""
517 """return a "mode" suitable for v1 serialization"""
518 return self._mode if self._mode is not None else 0
518 return self._mode if self._mode is not None else 0
519
519
520 def _v1_size(self):
520 def _v1_size(self):
521 """return a "size" suitable for v1 serialization"""
521 """return a "size" suitable for v1 serialization"""
522 if not self.any_tracked:
522 if not self.any_tracked:
523 # the object has no state to record, this is -currently-
523 # the object has no state to record, this is -currently-
524 # unsupported
524 # unsupported
525 raise RuntimeError('untracked item')
525 raise RuntimeError('untracked item')
526 elif self.removed and self._p1_tracked and self._p2_info:
526 elif self.removed and self._p1_tracked and self._p2_info:
527 return NONNORMAL
527 return NONNORMAL
528 elif self._p2_info:
528 elif self._p2_info:
529 return FROM_P2
529 return FROM_P2
530 elif self.removed:
530 elif self.removed:
531 return 0
531 return 0
532 elif self.added:
532 elif self.added:
533 return NONNORMAL
533 return NONNORMAL
534 elif self._size is None:
534 elif self._size is None:
535 return NONNORMAL
535 return NONNORMAL
536 else:
536 else:
537 return self._size
537 return self._size
538
538
539 def _v1_mtime(self):
539 def _v1_mtime(self):
540 """return a "mtime" suitable for v1 serialization"""
540 """return a "mtime" suitable for v1 serialization"""
541 if not self.any_tracked:
541 if not self.any_tracked:
542 # the object has no state to record, this is -currently-
542 # the object has no state to record, this is -currently-
543 # unsupported
543 # unsupported
544 raise RuntimeError('untracked item')
544 raise RuntimeError('untracked item')
545 elif self.removed:
545 elif self.removed:
546 return 0
546 return 0
547 elif self._mtime_s is None:
547 elif self._mtime_s is None:
548 return AMBIGUOUS_TIME
548 return AMBIGUOUS_TIME
549 elif self._p2_info:
549 elif self._p2_info:
550 return AMBIGUOUS_TIME
550 return AMBIGUOUS_TIME
551 elif not self._p1_tracked:
551 elif not self._p1_tracked:
552 return AMBIGUOUS_TIME
552 return AMBIGUOUS_TIME
553 elif self._mtime_second_ambiguous:
553 elif self._mtime_second_ambiguous:
554 return AMBIGUOUS_TIME
554 return AMBIGUOUS_TIME
555 else:
555 else:
556 return self._mtime_s
556 return self._mtime_s
557
557
558
558
559 def gettype(q):
559 def gettype(q):
560 return int(q & 0xFFFF)
560 return int(q & 0xFFFF)
561
561
562
562
563 class BaseIndexObject:
563 class BaseIndexObject:
564 # Can I be passed to an algorithme implemented in Rust ?
564 # Can I be passed to an algorithme implemented in Rust ?
565 rust_ext_compat = 0
565 rust_ext_compat = 0
566 # Format of an index entry according to Python's `struct` language
566 # Format of an index entry according to Python's `struct` language
567 index_format = revlog_constants.INDEX_ENTRY_V1
567 index_format = revlog_constants.INDEX_ENTRY_V1
568 # Size of a C unsigned long long int, platform independent
568 # Size of a C unsigned long long int, platform independent
569 big_int_size = struct.calcsize(b'>Q')
569 big_int_size = struct.calcsize(b'>Q')
570 # Size of a C long int, platform independent
570 # Size of a C long int, platform independent
571 int_size = struct.calcsize(b'>i')
571 int_size = struct.calcsize(b'>i')
572 # An empty index entry, used as a default value to be overridden, or nullrev
572 # An empty index entry, used as a default value to be overridden, or nullrev
573 null_item = (
573 null_item = (
574 0,
574 0,
575 0,
575 0,
576 0,
576 0,
577 -1,
577 -1,
578 -1,
578 -1,
579 -1,
579 -1,
580 -1,
580 -1,
581 sha1nodeconstants.nullid,
581 sha1nodeconstants.nullid,
582 0,
582 0,
583 0,
583 0,
584 revlog_constants.COMP_MODE_INLINE,
584 revlog_constants.COMP_MODE_INLINE,
585 revlog_constants.COMP_MODE_INLINE,
585 revlog_constants.COMP_MODE_INLINE,
586 revlog_constants.RANK_UNKNOWN,
586 revlog_constants.RANK_UNKNOWN,
587 )
587 )
588
588
589 @util.propertycache
589 @util.propertycache
590 def entry_size(self):
590 def entry_size(self):
591 return self.index_format.size
591 return self.index_format.size
592
592
593 @util.propertycache
593 @util.propertycache
594 def _nodemap(self):
594 def _nodemap(self):
595 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
595 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
596 for r in range(0, len(self)):
596 for r in range(0, len(self)):
597 n = self[r][7]
597 n = self[r][7]
598 nodemap[n] = r
598 nodemap[n] = r
599 return nodemap
599 return nodemap
600
600
601 def has_node(self, node):
601 def has_node(self, node):
602 """return True if the node exist in the index"""
602 """return True if the node exist in the index"""
603 return node in self._nodemap
603 return node in self._nodemap
604
604
605 def rev(self, node):
605 def rev(self, node):
606 """return a revision for a node
606 """return a revision for a node
607
607
608 If the node is unknown, raise a RevlogError"""
608 If the node is unknown, raise a RevlogError"""
609 return self._nodemap[node]
609 return self._nodemap[node]
610
610
611 def get_rev(self, node):
611 def get_rev(self, node):
612 """return a revision for a node
612 """return a revision for a node
613
613
614 If the node is unknown, return None"""
614 If the node is unknown, return None"""
615 return self._nodemap.get(node)
615 return self._nodemap.get(node)
616
616
617 def _stripnodes(self, start):
617 def _stripnodes(self, start):
618 if '_nodemap' in vars(self):
618 if '_nodemap' in vars(self):
619 for r in range(start, len(self)):
619 for r in range(start, len(self)):
620 n = self[r][7]
620 n = self[r][7]
621 del self._nodemap[n]
621 del self._nodemap[n]
622
622
623 def clearcaches(self):
623 def clearcaches(self):
624 self.__dict__.pop('_nodemap', None)
624 self.__dict__.pop('_nodemap', None)
625
625
626 def __len__(self):
626 def __len__(self):
627 return self._lgt + len(self._extra)
627 return self._lgt + len(self._extra)
628
628
629 def append(self, tup):
629 def append(self, tup):
630 if '_nodemap' in vars(self):
630 if '_nodemap' in vars(self):
631 self._nodemap[tup[7]] = len(self)
631 self._nodemap[tup[7]] = len(self)
632 data = self._pack_entry(len(self), tup)
632 data = self._pack_entry(len(self), tup)
633 self._extra.append(data)
633 self._extra.append(data)
634
634
635 def _pack_entry(self, rev, entry):
635 def _pack_entry(self, rev, entry):
636 assert entry[8] == 0
636 assert entry[8] == 0
637 assert entry[9] == 0
637 assert entry[9] == 0
638 return self.index_format.pack(*entry[:8])
638 return self.index_format.pack(*entry[:8])
639
639
640 def _check_index(self, i):
640 def _check_index(self, i):
641 if not isinstance(i, int):
641 if not isinstance(i, int):
642 raise TypeError(b"expecting int indexes")
642 raise TypeError(b"expecting int indexes")
643 if i < 0 or i >= len(self):
643 if i < 0 or i >= len(self):
644 raise IndexError(i)
644 raise IndexError(i)
645
645
646 def __getitem__(self, i):
646 def __getitem__(self, i):
647 if i == -1:
647 if i == -1:
648 return self.null_item
648 return self.null_item
649 self._check_index(i)
649 self._check_index(i)
650 if i >= self._lgt:
650 if i >= self._lgt:
651 data = self._extra[i - self._lgt]
651 data = self._extra[i - self._lgt]
652 else:
652 else:
653 index = self._calculate_index(i)
653 index = self._calculate_index(i)
654 data = self._data[index : index + self.entry_size]
654 data = self._data[index : index + self.entry_size]
655 r = self._unpack_entry(i, data)
655 r = self._unpack_entry(i, data)
656 if self._lgt and i == 0:
656 if self._lgt and i == 0:
657 offset = revlogutils.offset_type(0, gettype(r[0]))
657 offset = revlogutils.offset_type(0, gettype(r[0]))
658 r = (offset,) + r[1:]
658 r = (offset,) + r[1:]
659 return r
659 return r
660
660
661 def _unpack_entry(self, rev, data):
661 def _unpack_entry(self, rev, data):
662 r = self.index_format.unpack(data)
662 r = self.index_format.unpack(data)
663 r = r + (
663 r = r + (
664 0,
664 0,
665 0,
665 0,
666 revlog_constants.COMP_MODE_INLINE,
666 revlog_constants.COMP_MODE_INLINE,
667 revlog_constants.COMP_MODE_INLINE,
667 revlog_constants.COMP_MODE_INLINE,
668 revlog_constants.RANK_UNKNOWN,
668 revlog_constants.RANK_UNKNOWN,
669 )
669 )
670 return r
670 return r
671
671
672 def pack_header(self, header):
672 def pack_header(self, header):
673 """pack header information as binary"""
673 """pack header information as binary"""
674 v_fmt = revlog_constants.INDEX_HEADER
674 v_fmt = revlog_constants.INDEX_HEADER
675 return v_fmt.pack(header)
675 return v_fmt.pack(header)
676
676
677 def entry_binary(self, rev):
677 def entry_binary(self, rev):
678 """return the raw binary string representing a revision"""
678 """return the raw binary string representing a revision"""
679 entry = self[rev]
679 entry = self[rev]
680 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
680 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
681 if rev == 0:
681 if rev == 0:
682 p = p[revlog_constants.INDEX_HEADER.size :]
682 p = p[revlog_constants.INDEX_HEADER.size :]
683 return p
683 return p
684
684
685
685
686 class IndexObject(BaseIndexObject):
686 class IndexObject(BaseIndexObject):
687 def __init__(self, data):
687 def __init__(self, data):
688 assert len(data) % self.entry_size == 0, (
688 assert len(data) % self.entry_size == 0, (
689 len(data),
689 len(data),
690 self.entry_size,
690 self.entry_size,
691 len(data) % self.entry_size,
691 len(data) % self.entry_size,
692 )
692 )
693 self._data = data
693 self._data = data
694 self._lgt = len(data) // self.entry_size
694 self._lgt = len(data) // self.entry_size
695 self._extra = []
695 self._extra = []
696
696
697 def _calculate_index(self, i):
697 def _calculate_index(self, i):
698 return i * self.entry_size
698 return i * self.entry_size
699
699
700 def __delitem__(self, i):
700 def __delitem__(self, i):
701 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
701 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
702 raise ValueError(b"deleting slices only supports a:-1 with step 1")
702 raise ValueError(b"deleting slices only supports a:-1 with step 1")
703 i = i.start
703 i = i.start
704 self._check_index(i)
704 self._check_index(i)
705 self._stripnodes(i)
705 self._stripnodes(i)
706 if i < self._lgt:
706 if i < self._lgt:
707 self._data = self._data[: i * self.entry_size]
707 self._data = self._data[: i * self.entry_size]
708 self._lgt = i
708 self._lgt = i
709 self._extra = []
709 self._extra = []
710 else:
710 else:
711 self._extra = self._extra[: i - self._lgt]
711 self._extra = self._extra[: i - self._lgt]
712
712
713
713
714 class PersistentNodeMapIndexObject(IndexObject):
714 class PersistentNodeMapIndexObject(IndexObject):
715 """a Debug oriented class to test persistent nodemap
715 """a Debug oriented class to test persistent nodemap
716
716
717 We need a simple python object to test API and higher level behavior. See
717 We need a simple python object to test API and higher level behavior. See
718 the Rust implementation for more serious usage. This should be used only
718 the Rust implementation for more serious usage. This should be used only
719 through the dedicated `devel.persistent-nodemap` config.
719 through the dedicated `devel.persistent-nodemap` config.
720 """
720 """
721
721
722 def nodemap_data_all(self):
722 def nodemap_data_all(self):
723 """Return bytes containing a full serialization of a nodemap
723 """Return bytes containing a full serialization of a nodemap
724
724
725 The nodemap should be valid for the full set of revisions in the
725 The nodemap should be valid for the full set of revisions in the
726 index."""
726 index."""
727 return nodemaputil.persistent_data(self)
727 return nodemaputil.persistent_data(self)
728
728
729 def nodemap_data_incremental(self):
729 def nodemap_data_incremental(self):
730 """Return bytes containing a incremental update to persistent nodemap
730 """Return bytes containing a incremental update to persistent nodemap
731
731
732 This containst the data for an append-only update of the data provided
732 This containst the data for an append-only update of the data provided
733 in the last call to `update_nodemap_data`.
733 in the last call to `update_nodemap_data`.
734 """
734 """
735 if self._nm_root is None:
735 if self._nm_root is None:
736 return None
736 return None
737 docket = self._nm_docket
737 docket = self._nm_docket
738 changed, data = nodemaputil.update_persistent_data(
738 changed, data = nodemaputil.update_persistent_data(
739 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
739 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
740 )
740 )
741
741
742 self._nm_root = self._nm_max_idx = self._nm_docket = None
742 self._nm_root = self._nm_max_idx = self._nm_docket = None
743 return docket, changed, data
743 return docket, changed, data
744
744
745 def update_nodemap_data(self, docket, nm_data):
745 def update_nodemap_data(self, docket, nm_data):
746 """provide full block of persisted binary data for a nodemap
746 """provide full block of persisted binary data for a nodemap
747
747
748 The data are expected to come from disk. See `nodemap_data_all` for a
748 The data are expected to come from disk. See `nodemap_data_all` for a
749 produceur of such data."""
749 produceur of such data."""
750 if nm_data is not None:
750 if nm_data is not None:
751 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
751 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
752 if self._nm_root:
752 if self._nm_root:
753 self._nm_docket = docket
753 self._nm_docket = docket
754 else:
754 else:
755 self._nm_root = self._nm_max_idx = self._nm_docket = None
755 self._nm_root = self._nm_max_idx = self._nm_docket = None
756
756
757
757
758 class InlinedIndexObject(BaseIndexObject):
758 class InlinedIndexObject(BaseIndexObject):
759 def __init__(self, data, inline=0):
759 def __init__(self, data, inline=0):
760 self._data = data
760 self._data = data
761 self._lgt = self._inline_scan(None)
761 self._lgt = self._inline_scan(None)
762 self._inline_scan(self._lgt)
762 self._inline_scan(self._lgt)
763 self._extra = []
763 self._extra = []
764
764
765 def _inline_scan(self, lgt):
765 def _inline_scan(self, lgt):
766 off = 0
766 off = 0
767 if lgt is not None:
767 if lgt is not None:
768 self._offsets = [0] * lgt
768 self._offsets = [0] * lgt
769 count = 0
769 count = 0
770 while off <= len(self._data) - self.entry_size:
770 while off <= len(self._data) - self.entry_size:
771 start = off + self.big_int_size
771 start = off + self.big_int_size
772 (s,) = struct.unpack(
772 (s,) = struct.unpack(
773 b'>i',
773 b'>i',
774 self._data[start : start + self.int_size],
774 self._data[start : start + self.int_size],
775 )
775 )
776 if lgt is not None:
776 if lgt is not None:
777 self._offsets[count] = off
777 self._offsets[count] = off
778 count += 1
778 count += 1
779 off += self.entry_size + s
779 off += self.entry_size + s
780 if off != len(self._data):
780 if off != len(self._data):
781 raise ValueError(b"corrupted data")
781 raise ValueError(b"corrupted data")
782 return count
782 return count
783
783
784 def __delitem__(self, i):
784 def __delitem__(self, i):
785 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
785 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
786 raise ValueError(b"deleting slices only supports a:-1 with step 1")
786 raise ValueError(b"deleting slices only supports a:-1 with step 1")
787 i = i.start
787 i = i.start
788 self._check_index(i)
788 self._check_index(i)
789 self._stripnodes(i)
789 self._stripnodes(i)
790 if i < self._lgt:
790 if i < self._lgt:
791 self._offsets = self._offsets[:i]
791 self._offsets = self._offsets[:i]
792 self._lgt = i
792 self._lgt = i
793 self._extra = []
793 self._extra = []
794 else:
794 else:
795 self._extra = self._extra[: i - self._lgt]
795 self._extra = self._extra[: i - self._lgt]
796
796
797 def _calculate_index(self, i):
797 def _calculate_index(self, i):
798 return self._offsets[i]
798 return self._offsets[i]
799
799
800
800
801 def parse_index2(data, inline, format=revlog_constants.REVLOGV1):
801 def parse_index2(data, inline, format=revlog_constants.REVLOGV1):
802 if format == revlog_constants.CHANGELOGV2:
802 if format == revlog_constants.CHANGELOGV2:
803 return parse_index_cl_v2(data)
803 return parse_index_cl_v2(data)
804 if not inline:
804 if not inline:
805 if format == revlog_constants.REVLOGV2:
805 if format == revlog_constants.REVLOGV2:
806 cls = IndexObject2
806 cls = IndexObject2
807 else:
807 else:
808 cls = IndexObject
808 cls = IndexObject
809 return cls(data), None
809 return cls(data), None
810 cls = InlinedIndexObject
810 cls = InlinedIndexObject
811 return cls(data, inline), (0, data)
811 return cls(data, inline), (0, data)
812
812
813
813
814 def parse_index_cl_v2(data):
814 def parse_index_cl_v2(data):
815 return IndexChangelogV2(data), None
815 return IndexChangelogV2(data), None
816
816
817
817
818 class IndexObject2(IndexObject):
818 class IndexObject2(IndexObject):
819 index_format = revlog_constants.INDEX_ENTRY_V2
819 index_format = revlog_constants.INDEX_ENTRY_V2
820
820
821 def replace_sidedata_info(
821 def replace_sidedata_info(
822 self,
822 self,
823 rev,
823 rev,
824 sidedata_offset,
824 sidedata_offset,
825 sidedata_length,
825 sidedata_length,
826 offset_flags,
826 offset_flags,
827 compression_mode,
827 compression_mode,
828 ):
828 ):
829 """
829 """
830 Replace an existing index entry's sidedata offset and length with new
830 Replace an existing index entry's sidedata offset and length with new
831 ones.
831 ones.
832 This cannot be used outside of the context of sidedata rewriting,
832 This cannot be used outside of the context of sidedata rewriting,
833 inside the transaction that creates the revision `rev`.
833 inside the transaction that creates the revision `rev`.
834 """
834 """
835 if rev < 0:
835 if rev < 0:
836 raise KeyError
836 raise KeyError
837 self._check_index(rev)
837 self._check_index(rev)
838 if rev < self._lgt:
838 if rev < self._lgt:
839 msg = b"cannot rewrite entries outside of this transaction"
839 msg = b"cannot rewrite entries outside of this transaction"
840 raise KeyError(msg)
840 raise KeyError(msg)
841 else:
841 else:
842 entry = list(self[rev])
842 entry = list(self[rev])
843 entry[0] = offset_flags
843 entry[0] = offset_flags
844 entry[8] = sidedata_offset
844 entry[8] = sidedata_offset
845 entry[9] = sidedata_length
845 entry[9] = sidedata_length
846 entry[11] = compression_mode
846 entry[11] = compression_mode
847 entry = tuple(entry)
847 entry = tuple(entry)
848 new = self._pack_entry(rev, entry)
848 new = self._pack_entry(rev, entry)
849 self._extra[rev - self._lgt] = new
849 self._extra[rev - self._lgt] = new
850
850
851 def _unpack_entry(self, rev, data):
851 def _unpack_entry(self, rev, data):
852 data = self.index_format.unpack(data)
852 data = self.index_format.unpack(data)
853 entry = data[:10]
853 entry = data[:10]
854 data_comp = data[10] & 3
854 data_comp = data[10] & 3
855 sidedata_comp = (data[10] & (3 << 2)) >> 2
855 sidedata_comp = (data[10] & (3 << 2)) >> 2
856 return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN)
856 return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN)
857
857
858 def _pack_entry(self, rev, entry):
858 def _pack_entry(self, rev, entry):
859 data = entry[:10]
859 data = entry[:10]
860 data_comp = entry[10] & 3
860 data_comp = entry[10] & 3
861 sidedata_comp = (entry[11] & 3) << 2
861 sidedata_comp = (entry[11] & 3) << 2
862 data += (data_comp | sidedata_comp,)
862 data += (data_comp | sidedata_comp,)
863
863
864 return self.index_format.pack(*data)
864 return self.index_format.pack(*data)
865
865
866 def entry_binary(self, rev):
866 def entry_binary(self, rev):
867 """return the raw binary string representing a revision"""
867 """return the raw binary string representing a revision"""
868 entry = self[rev]
868 entry = self[rev]
869 return self._pack_entry(rev, entry)
869 return self._pack_entry(rev, entry)
870
870
871 def pack_header(self, header):
871 def pack_header(self, header):
872 """pack header information as binary"""
872 """pack header information as binary"""
873 msg = 'version header should go in the docket, not the index: %d'
873 msg = 'version header should go in the docket, not the index: %d'
874 msg %= header
874 msg %= header
875 raise error.ProgrammingError(msg)
875 raise error.ProgrammingError(msg)
876
876
877
877
878 class IndexChangelogV2(IndexObject2):
878 class IndexChangelogV2(IndexObject2):
879 index_format = revlog_constants.INDEX_ENTRY_CL_V2
879 index_format = revlog_constants.INDEX_ENTRY_CL_V2
880
880
881 null_item = (
881 null_item = (
882 IndexObject2.null_item[: revlog_constants.ENTRY_RANK]
882 IndexObject2.null_item[: revlog_constants.ENTRY_RANK]
883 + (0,) # rank of null is 0
883 + (0,) # rank of null is 0
884 + IndexObject2.null_item[revlog_constants.ENTRY_RANK :]
884 + IndexObject2.null_item[revlog_constants.ENTRY_RANK :]
885 )
885 )
886
886
887 def _unpack_entry(self, rev, data, r=True):
887 def _unpack_entry(self, rev, data, r=True):
888 items = self.index_format.unpack(data)
888 items = self.index_format.unpack(data)
889 return (
889 return (
890 items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET],
890 items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET],
891 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH],
891 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH],
892 items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH],
892 items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH],
893 rev,
893 rev,
894 rev,
894 rev,
895 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1],
895 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1],
896 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2],
896 items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2],
897 items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID],
897 items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID],
898 items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET],
898 items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET],
899 items[
899 items[
900 revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH
900 revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH
901 ],
901 ],
902 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3,
902 items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3,
903 (items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2)
903 (items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2)
904 & 3,
904 & 3,
905 items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK],
905 items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK],
906 )
906 )
907
907
908 def _pack_entry(self, rev, entry):
908 def _pack_entry(self, rev, entry):
909
909
910 base = entry[revlog_constants.ENTRY_DELTA_BASE]
910 base = entry[revlog_constants.ENTRY_DELTA_BASE]
911 link_rev = entry[revlog_constants.ENTRY_LINK_REV]
911 link_rev = entry[revlog_constants.ENTRY_LINK_REV]
912 assert base == rev, (base, rev)
912 assert base == rev, (base, rev)
913 assert link_rev == rev, (link_rev, rev)
913 assert link_rev == rev, (link_rev, rev)
914 data = (
914 data = (
915 entry[revlog_constants.ENTRY_DATA_OFFSET],
915 entry[revlog_constants.ENTRY_DATA_OFFSET],
916 entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH],
916 entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH],
917 entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH],
917 entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH],
918 entry[revlog_constants.ENTRY_PARENT_1],
918 entry[revlog_constants.ENTRY_PARENT_1],
919 entry[revlog_constants.ENTRY_PARENT_2],
919 entry[revlog_constants.ENTRY_PARENT_2],
920 entry[revlog_constants.ENTRY_NODE_ID],
920 entry[revlog_constants.ENTRY_NODE_ID],
921 entry[revlog_constants.ENTRY_SIDEDATA_OFFSET],
921 entry[revlog_constants.ENTRY_SIDEDATA_OFFSET],
922 entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH],
922 entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH],
923 entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3
923 entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3
924 | (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3)
924 | (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3)
925 << 2,
925 << 2,
926 entry[revlog_constants.ENTRY_RANK],
926 entry[revlog_constants.ENTRY_RANK],
927 )
927 )
928 return self.index_format.pack(*data)
928 return self.index_format.pack(*data)
929
929
930
930
931 def parse_index_devel_nodemap(data, inline):
931 def parse_index_devel_nodemap(data, inline):
932 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
932 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
933 return PersistentNodeMapIndexObject(data), None
933 return PersistentNodeMapIndexObject(data), None
934
934
935
935
936 def parse_dirstate(dmap, copymap, st):
936 def parse_dirstate(dmap, copymap, st):
937 parents = [st[:20], st[20:40]]
937 parents = [st[:20], st[20:40]]
938 # dereference fields so they will be local in loop
938 # dereference fields so they will be local in loop
939 format = b">cllll"
939 format = b">cllll"
940 e_size = struct.calcsize(format)
940 e_size = struct.calcsize(format)
941 pos1 = 40
941 pos1 = 40
942 l = len(st)
942 l = len(st)
943
943
944 # the inner loop
944 # the inner loop
945 while pos1 < l:
945 while pos1 < l:
946 pos2 = pos1 + e_size
946 pos2 = pos1 + e_size
947 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
947 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
948 pos1 = pos2 + e[4]
948 pos1 = pos2 + e[4]
949 f = st[pos2:pos1]
949 f = st[pos2:pos1]
950 if b'\0' in f:
950 if b'\0' in f:
951 f, c = f.split(b'\0')
951 f, c = f.split(b'\0')
952 copymap[f] = c
952 copymap[f] = c
953 dmap[f] = DirstateItem.from_v1_data(*e[:4])
953 dmap[f] = DirstateItem.from_v1_data(*e[:4])
954 return parents
954 return parents
955
955
956
956
957 def pack_dirstate(dmap, copymap, pl):
957 def pack_dirstate(dmap, copymap, pl):
958 cs = stringio()
958 cs = stringio()
959 write = cs.write
959 write = cs.write
960 write(b"".join(pl))
960 write(b"".join(pl))
961 for f, e in dmap.items():
961 for f, e in dmap.items():
962 if f in copymap:
962 if f in copymap:
963 f = b"%s\0%s" % (f, copymap[f])
963 f = b"%s\0%s" % (f, copymap[f])
964 e = _pack(
964 e = _pack(
965 b">cllll",
965 b">cllll",
966 e._v1_state(),
966 e._v1_state(),
967 e._v1_mode(),
967 e._v1_mode(),
968 e._v1_size(),
968 e._v1_size(),
969 e._v1_mtime(),
969 e._v1_mtime(),
970 len(f),
970 len(f),
971 )
971 )
972 write(e)
972 write(e)
973 write(f)
973 write(f)
974 return cs.getvalue()
974 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now