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