##// END OF EJS Templates
dirstate-entry: use `?` for the state of entry without any tracking...
marmoute -
r48900:418611f1 default
parent child Browse files
Show More
@@ -1,824 +1,826 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 struct
10 import struct
11 import zlib
11 import zlib
12
12
13 from ..node import (
13 from ..node import (
14 nullrev,
14 nullrev,
15 sha1nodeconstants,
15 sha1nodeconstants,
16 )
16 )
17 from ..thirdparty import attr
17 from ..thirdparty import attr
18 from .. import (
18 from .. import (
19 error,
19 error,
20 pycompat,
20 pycompat,
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 = pycompat.bytesio
28 stringio = pycompat.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
46
47 @attr.s(slots=True, init=False)
47 @attr.s(slots=True, init=False)
48 class DirstateItem(object):
48 class DirstateItem(object):
49 """represent a dirstate entry
49 """represent a dirstate entry
50
50
51 It contains:
51 It contains:
52
52
53 - state (one of 'n', 'a', 'r', 'm')
53 - state (one of 'n', 'a', 'r', 'm')
54 - mode,
54 - mode,
55 - size,
55 - size,
56 - mtime,
56 - mtime,
57 """
57 """
58
58
59 _wc_tracked = attr.ib()
59 _wc_tracked = attr.ib()
60 _p1_tracked = attr.ib()
60 _p1_tracked = attr.ib()
61 _p2_tracked = attr.ib()
61 _p2_tracked = attr.ib()
62 # the three item above should probably be combined
62 # the three item above should probably be combined
63 #
63 #
64 # However it is unclear if they properly cover some of the most advanced
64 # However it is unclear if they properly cover some of the most advanced
65 # merge case. So we should probably wait on this to be settled.
65 # merge case. So we should probably wait on this to be settled.
66 _merged = attr.ib()
66 _merged = attr.ib()
67 _clean_p1 = attr.ib()
67 _clean_p1 = attr.ib()
68 _clean_p2 = attr.ib()
68 _clean_p2 = attr.ib()
69 _possibly_dirty = attr.ib()
69 _possibly_dirty = attr.ib()
70 _mode = attr.ib()
70 _mode = attr.ib()
71 _size = attr.ib()
71 _size = attr.ib()
72 _mtime = attr.ib()
72 _mtime = attr.ib()
73
73
74 def __init__(
74 def __init__(
75 self,
75 self,
76 wc_tracked=False,
76 wc_tracked=False,
77 p1_tracked=False,
77 p1_tracked=False,
78 p2_tracked=False,
78 p2_tracked=False,
79 merged=False,
79 merged=False,
80 clean_p1=False,
80 clean_p1=False,
81 clean_p2=False,
81 clean_p2=False,
82 possibly_dirty=False,
82 possibly_dirty=False,
83 parentfiledata=None,
83 parentfiledata=None,
84 ):
84 ):
85 if merged and (clean_p1 or clean_p2):
85 if merged and (clean_p1 or clean_p2):
86 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
86 msg = b'`merged` argument incompatible with `clean_p1`/`clean_p2`'
87 raise error.ProgrammingError(msg)
87 raise error.ProgrammingError(msg)
88
88
89 self._wc_tracked = wc_tracked
89 self._wc_tracked = wc_tracked
90 self._p1_tracked = p1_tracked
90 self._p1_tracked = p1_tracked
91 self._p2_tracked = p2_tracked
91 self._p2_tracked = p2_tracked
92 self._merged = merged
92 self._merged = merged
93 self._clean_p1 = clean_p1
93 self._clean_p1 = clean_p1
94 self._clean_p2 = clean_p2
94 self._clean_p2 = clean_p2
95 self._possibly_dirty = possibly_dirty
95 self._possibly_dirty = possibly_dirty
96 if parentfiledata is None:
96 if parentfiledata is None:
97 self._mode = None
97 self._mode = None
98 self._size = None
98 self._size = None
99 self._mtime = None
99 self._mtime = None
100 else:
100 else:
101 self._mode = parentfiledata[0]
101 self._mode = parentfiledata[0]
102 self._size = parentfiledata[1]
102 self._size = parentfiledata[1]
103 self._mtime = parentfiledata[2]
103 self._mtime = parentfiledata[2]
104
104
105 @classmethod
105 @classmethod
106 def new_added(cls):
106 def new_added(cls):
107 """constructor to help legacy API to build a new "added" item
107 """constructor to help legacy API to build a new "added" item
108
108
109 Should eventually be removed
109 Should eventually be removed
110 """
110 """
111 instance = cls()
111 instance = cls()
112 instance._wc_tracked = True
112 instance._wc_tracked = True
113 instance._p1_tracked = False
113 instance._p1_tracked = False
114 instance._p2_tracked = False
114 instance._p2_tracked = False
115 return instance
115 return instance
116
116
117 @classmethod
117 @classmethod
118 def new_merged(cls):
118 def new_merged(cls):
119 """constructor to help legacy API to build a new "merged" item
119 """constructor to help legacy API to build a new "merged" item
120
120
121 Should eventually be removed
121 Should eventually be removed
122 """
122 """
123 instance = cls()
123 instance = cls()
124 instance._wc_tracked = True
124 instance._wc_tracked = True
125 instance._p1_tracked = True # might not be True because of rename ?
125 instance._p1_tracked = True # might not be True because of rename ?
126 instance._p2_tracked = True # might not be True because of rename ?
126 instance._p2_tracked = True # might not be True because of rename ?
127 instance._merged = True
127 instance._merged = True
128 return instance
128 return instance
129
129
130 @classmethod
130 @classmethod
131 def new_from_p2(cls):
131 def new_from_p2(cls):
132 """constructor to help legacy API to build a new "from_p2" item
132 """constructor to help legacy API to build a new "from_p2" item
133
133
134 Should eventually be removed
134 Should eventually be removed
135 """
135 """
136 instance = cls()
136 instance = cls()
137 instance._wc_tracked = True
137 instance._wc_tracked = True
138 instance._p1_tracked = False # might actually be True
138 instance._p1_tracked = False # might actually be True
139 instance._p2_tracked = True
139 instance._p2_tracked = True
140 instance._clean_p2 = True
140 instance._clean_p2 = True
141 return instance
141 return instance
142
142
143 @classmethod
143 @classmethod
144 def new_possibly_dirty(cls):
144 def new_possibly_dirty(cls):
145 """constructor to help legacy API to build a new "possibly_dirty" item
145 """constructor to help legacy API to build a new "possibly_dirty" item
146
146
147 Should eventually be removed
147 Should eventually be removed
148 """
148 """
149 instance = cls()
149 instance = cls()
150 instance._wc_tracked = True
150 instance._wc_tracked = True
151 instance._p1_tracked = True
151 instance._p1_tracked = True
152 instance._possibly_dirty = True
152 instance._possibly_dirty = True
153 return instance
153 return instance
154
154
155 @classmethod
155 @classmethod
156 def new_normal(cls, mode, size, mtime):
156 def new_normal(cls, mode, size, mtime):
157 """constructor to help legacy API to build a new "normal" item
157 """constructor to help legacy API to build a new "normal" item
158
158
159 Should eventually be removed
159 Should eventually be removed
160 """
160 """
161 assert size != FROM_P2
161 assert size != FROM_P2
162 assert size != NONNORMAL
162 assert size != NONNORMAL
163 instance = cls()
163 instance = cls()
164 instance._wc_tracked = True
164 instance._wc_tracked = True
165 instance._p1_tracked = True
165 instance._p1_tracked = True
166 instance._mode = mode
166 instance._mode = mode
167 instance._size = size
167 instance._size = size
168 instance._mtime = mtime
168 instance._mtime = mtime
169 return instance
169 return instance
170
170
171 @classmethod
171 @classmethod
172 def from_v1_data(cls, state, mode, size, mtime):
172 def from_v1_data(cls, state, mode, size, mtime):
173 """Build a new DirstateItem object from V1 data
173 """Build a new DirstateItem object from V1 data
174
174
175 Since the dirstate-v1 format is frozen, the signature of this function
175 Since the dirstate-v1 format is frozen, the signature of this function
176 is not expected to change, unlike the __init__ one.
176 is not expected to change, unlike the __init__ one.
177 """
177 """
178 if state == b'm':
178 if state == b'm':
179 return cls.new_merged()
179 return cls.new_merged()
180 elif state == b'a':
180 elif state == b'a':
181 return cls.new_added()
181 return cls.new_added()
182 elif state == b'r':
182 elif state == b'r':
183 instance = cls()
183 instance = cls()
184 instance._wc_tracked = False
184 instance._wc_tracked = False
185 if size == NONNORMAL:
185 if size == NONNORMAL:
186 instance._merged = True
186 instance._merged = True
187 instance._p1_tracked = (
187 instance._p1_tracked = (
188 True # might not be True because of rename ?
188 True # might not be True because of rename ?
189 )
189 )
190 instance._p2_tracked = (
190 instance._p2_tracked = (
191 True # might not be True because of rename ?
191 True # might not be True because of rename ?
192 )
192 )
193 elif size == FROM_P2:
193 elif size == FROM_P2:
194 instance._clean_p2 = True
194 instance._clean_p2 = True
195 instance._p1_tracked = (
195 instance._p1_tracked = (
196 False # We actually don't know (file history)
196 False # We actually don't know (file history)
197 )
197 )
198 instance._p2_tracked = True
198 instance._p2_tracked = True
199 else:
199 else:
200 instance._p1_tracked = True
200 instance._p1_tracked = True
201 return instance
201 return instance
202 elif state == b'n':
202 elif state == b'n':
203 if size == FROM_P2:
203 if size == FROM_P2:
204 return cls.new_from_p2()
204 return cls.new_from_p2()
205 elif size == NONNORMAL:
205 elif size == NONNORMAL:
206 return cls.new_possibly_dirty()
206 return cls.new_possibly_dirty()
207 elif mtime == AMBIGUOUS_TIME:
207 elif mtime == AMBIGUOUS_TIME:
208 instance = cls.new_normal(mode, size, 42)
208 instance = cls.new_normal(mode, size, 42)
209 instance._mtime = None
209 instance._mtime = None
210 instance._possibly_dirty = True
210 instance._possibly_dirty = True
211 return instance
211 return instance
212 else:
212 else:
213 return cls.new_normal(mode, size, mtime)
213 return cls.new_normal(mode, size, mtime)
214 else:
214 else:
215 raise RuntimeError(b'unknown state: %s' % state)
215 raise RuntimeError(b'unknown state: %s' % state)
216
216
217 def set_possibly_dirty(self):
217 def set_possibly_dirty(self):
218 """Mark a file as "possibly dirty"
218 """Mark a file as "possibly dirty"
219
219
220 This means the next status call will have to actually check its content
220 This means the next status call will have to actually check its content
221 to make sure it is correct.
221 to make sure it is correct.
222 """
222 """
223 self._possibly_dirty = True
223 self._possibly_dirty = True
224
224
225 def set_clean(self, mode, size, mtime):
225 def set_clean(self, mode, size, mtime):
226 """mark a file as "clean" cancelling potential "possibly dirty call"
226 """mark a file as "clean" cancelling potential "possibly dirty call"
227
227
228 Note: this function is a descendant of `dirstate.normal` and is
228 Note: this function is a descendant of `dirstate.normal` and is
229 currently expected to be call on "normal" entry only. There are not
229 currently expected to be call on "normal" entry only. There are not
230 reason for this to not change in the future as long as the ccode is
230 reason for this to not change in the future as long as the ccode is
231 updated to preserve the proper state of the non-normal files.
231 updated to preserve the proper state of the non-normal files.
232 """
232 """
233 self._wc_tracked = True
233 self._wc_tracked = True
234 self._p1_tracked = True
234 self._p1_tracked = True
235 self._p2_tracked = False # this might be wrong
235 self._p2_tracked = False # this might be wrong
236 self._merged = False
236 self._merged = False
237 self._clean_p2 = False
237 self._clean_p2 = False
238 self._possibly_dirty = False
238 self._possibly_dirty = False
239 self._mode = mode
239 self._mode = mode
240 self._size = size
240 self._size = size
241 self._mtime = mtime
241 self._mtime = mtime
242
242
243 def set_tracked(self):
243 def set_tracked(self):
244 """mark a file as tracked in the working copy
244 """mark a file as tracked in the working copy
245
245
246 This will ultimately be called by command like `hg add`.
246 This will ultimately be called by command like `hg add`.
247 """
247 """
248 self._wc_tracked = True
248 self._wc_tracked = True
249 # `set_tracked` is replacing various `normallookup` call. So we set
249 # `set_tracked` is replacing various `normallookup` call. So we set
250 # "possibly dirty" to stay on the safe side.
250 # "possibly dirty" to stay on the safe side.
251 #
251 #
252 # Consider dropping this in the future in favor of something less broad.
252 # Consider dropping this in the future in favor of something less broad.
253 self._possibly_dirty = True
253 self._possibly_dirty = True
254
254
255 def set_untracked(self):
255 def set_untracked(self):
256 """mark a file as untracked in the working copy
256 """mark a file as untracked in the working copy
257
257
258 This will ultimately be called by command like `hg remove`.
258 This will ultimately be called by command like `hg remove`.
259 """
259 """
260 # backup the previous state (useful for merge)
260 # backup the previous state (useful for merge)
261 self._wc_tracked = False
261 self._wc_tracked = False
262 self._mode = None
262 self._mode = None
263 self._size = None
263 self._size = None
264 self._mtime = None
264 self._mtime = None
265
265
266 def drop_merge_data(self):
266 def drop_merge_data(self):
267 """remove all "merge-only" from a DirstateItem
267 """remove all "merge-only" from a DirstateItem
268
268
269 This is to be call by the dirstatemap code when the second parent is dropped
269 This is to be call by the dirstatemap code when the second parent is dropped
270 """
270 """
271 if not (self.merged or self.from_p2):
271 if not (self.merged or self.from_p2):
272 return
272 return
273 self._p1_tracked = self.merged # why is this not already properly set ?
273 self._p1_tracked = self.merged # why is this not already properly set ?
274
274
275 self._merged = False
275 self._merged = False
276 self._clean_p1 = False
276 self._clean_p1 = False
277 self._clean_p2 = False
277 self._clean_p2 = False
278 self._p2_tracked = False
278 self._p2_tracked = False
279 self._possibly_dirty = True
279 self._possibly_dirty = True
280 self._mode = None
280 self._mode = None
281 self._size = None
281 self._size = None
282 self._mtime = None
282 self._mtime = None
283
283
284 @property
284 @property
285 def mode(self):
285 def mode(self):
286 return self.v1_mode()
286 return self.v1_mode()
287
287
288 @property
288 @property
289 def size(self):
289 def size(self):
290 return self.v1_size()
290 return self.v1_size()
291
291
292 @property
292 @property
293 def mtime(self):
293 def mtime(self):
294 return self.v1_mtime()
294 return self.v1_mtime()
295
295
296 @property
296 @property
297 def state(self):
297 def state(self):
298 """
298 """
299 States are:
299 States are:
300 n normal
300 n normal
301 m needs merging
301 m needs merging
302 r marked for removal
302 r marked for removal
303 a marked for addition
303 a marked for addition
304
304
305 XXX This "state" is a bit obscure and mostly a direct expression of the
305 XXX This "state" is a bit obscure and mostly a direct expression of the
306 dirstatev1 format. It would make sense to ultimately deprecate it in
306 dirstatev1 format. It would make sense to ultimately deprecate it in
307 favor of the more "semantic" attributes.
307 favor of the more "semantic" attributes.
308 """
308 """
309 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
310 return b'?'
309 return self.v1_state()
311 return self.v1_state()
310
312
311 @property
313 @property
312 def tracked(self):
314 def tracked(self):
313 """True is the file is tracked in the working copy"""
315 """True is the file is tracked in the working copy"""
314 return self._wc_tracked
316 return self._wc_tracked
315
317
316 @property
318 @property
317 def any_tracked(self):
319 def any_tracked(self):
318 """True is the file is tracked anywhere (wc or parents)"""
320 """True is the file is tracked anywhere (wc or parents)"""
319 return self._wc_tracked or self._p1_tracked or self._p2_tracked
321 return self._wc_tracked or self._p1_tracked or self._p2_tracked
320
322
321 @property
323 @property
322 def added(self):
324 def added(self):
323 """True if the file has been added"""
325 """True if the file has been added"""
324 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked)
326 return self._wc_tracked and not (self._p1_tracked or self._p2_tracked)
325
327
326 @property
328 @property
327 def maybe_clean(self):
329 def maybe_clean(self):
328 """True if the file has a chance to be in the "clean" state"""
330 """True if the file has a chance to be in the "clean" state"""
329 if not self._wc_tracked:
331 if not self._wc_tracked:
330 return False
332 return False
331 elif self.added:
333 elif self.added:
332 return False
334 return False
333 elif self._merged:
335 elif self._merged:
334 return False
336 return False
335 elif self._clean_p2:
337 elif self._clean_p2:
336 return False
338 return False
337 return True
339 return True
338
340
339 @property
341 @property
340 def merged(self):
342 def merged(self):
341 """True if the file has been merged
343 """True if the file has been merged
342
344
343 Should only be set if a merge is in progress in the dirstate
345 Should only be set if a merge is in progress in the dirstate
344 """
346 """
345 return self._wc_tracked and self._merged
347 return self._wc_tracked and self._merged
346
348
347 @property
349 @property
348 def from_p2(self):
350 def from_p2(self):
349 """True if the file have been fetched from p2 during the current merge
351 """True if the file have been fetched from p2 during the current merge
350
352
351 This is only True is the file is currently tracked.
353 This is only True is the file is currently tracked.
352
354
353 Should only be set if a merge is in progress in the dirstate
355 Should only be set if a merge is in progress in the dirstate
354 """
356 """
355 if not self._wc_tracked:
357 if not self._wc_tracked:
356 return False
358 return False
357 return self._clean_p2
359 return self._clean_p2
358
360
359 @property
361 @property
360 def removed(self):
362 def removed(self):
361 """True if the file has been removed"""
363 """True if the file has been removed"""
362 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked)
364 return not self._wc_tracked and (self._p1_tracked or self._p2_tracked)
363
365
364 def v1_state(self):
366 def v1_state(self):
365 """return a "state" suitable for v1 serialization"""
367 """return a "state" suitable for v1 serialization"""
366 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
368 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
367 # the object has no state to record, this is -currently-
369 # the object has no state to record, this is -currently-
368 # unsupported
370 # unsupported
369 raise RuntimeError('untracked item')
371 raise RuntimeError('untracked item')
370 elif self.removed:
372 elif self.removed:
371 return b'r'
373 return b'r'
372 elif self.merged:
374 elif self.merged:
373 return b'm'
375 return b'm'
374 elif self.added:
376 elif self.added:
375 return b'a'
377 return b'a'
376 else:
378 else:
377 return b'n'
379 return b'n'
378
380
379 def v1_mode(self):
381 def v1_mode(self):
380 """return a "mode" suitable for v1 serialization"""
382 """return a "mode" suitable for v1 serialization"""
381 return self._mode if self._mode is not None else 0
383 return self._mode if self._mode is not None else 0
382
384
383 def v1_size(self):
385 def v1_size(self):
384 """return a "size" suitable for v1 serialization"""
386 """return a "size" suitable for v1 serialization"""
385 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
387 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
386 # the object has no state to record, this is -currently-
388 # the object has no state to record, this is -currently-
387 # unsupported
389 # unsupported
388 raise RuntimeError('untracked item')
390 raise RuntimeError('untracked item')
389 elif self.removed and self._merged:
391 elif self.removed and self._merged:
390 return NONNORMAL
392 return NONNORMAL
391 elif self.removed and self._clean_p2:
393 elif self.removed and self._clean_p2:
392 return FROM_P2
394 return FROM_P2
393 elif self.removed:
395 elif self.removed:
394 return 0
396 return 0
395 elif self.merged:
397 elif self.merged:
396 return FROM_P2
398 return FROM_P2
397 elif self.added:
399 elif self.added:
398 return NONNORMAL
400 return NONNORMAL
399 elif self.from_p2:
401 elif self.from_p2:
400 return FROM_P2
402 return FROM_P2
401 elif self._possibly_dirty:
403 elif self._possibly_dirty:
402 return self._size if self._size is not None else NONNORMAL
404 return self._size if self._size is not None else NONNORMAL
403 else:
405 else:
404 return self._size
406 return self._size
405
407
406 def v1_mtime(self):
408 def v1_mtime(self):
407 """return a "mtime" suitable for v1 serialization"""
409 """return a "mtime" suitable for v1 serialization"""
408 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
410 if not (self._p1_tracked or self._p2_tracked or self._wc_tracked):
409 # the object has no state to record, this is -currently-
411 # the object has no state to record, this is -currently-
410 # unsupported
412 # unsupported
411 raise RuntimeError('untracked item')
413 raise RuntimeError('untracked item')
412 elif self.removed:
414 elif self.removed:
413 return 0
415 return 0
414 elif self._possibly_dirty:
416 elif self._possibly_dirty:
415 return AMBIGUOUS_TIME
417 return AMBIGUOUS_TIME
416 elif self.merged:
418 elif self.merged:
417 return AMBIGUOUS_TIME
419 return AMBIGUOUS_TIME
418 elif self.added:
420 elif self.added:
419 return AMBIGUOUS_TIME
421 return AMBIGUOUS_TIME
420 elif self.from_p2:
422 elif self.from_p2:
421 return AMBIGUOUS_TIME
423 return AMBIGUOUS_TIME
422 else:
424 else:
423 return self._mtime if self._mtime is not None else 0
425 return self._mtime if self._mtime is not None else 0
424
426
425 def need_delay(self, now):
427 def need_delay(self, now):
426 """True if the stored mtime would be ambiguous with the current time"""
428 """True if the stored mtime would be ambiguous with the current time"""
427 return self.v1_state() == b'n' and self.v1_mtime() == now
429 return self.v1_state() == b'n' and self.v1_mtime() == now
428
430
429
431
430 def gettype(q):
432 def gettype(q):
431 return int(q & 0xFFFF)
433 return int(q & 0xFFFF)
432
434
433
435
434 class BaseIndexObject(object):
436 class BaseIndexObject(object):
435 # Can I be passed to an algorithme implemented in Rust ?
437 # Can I be passed to an algorithme implemented in Rust ?
436 rust_ext_compat = 0
438 rust_ext_compat = 0
437 # Format of an index entry according to Python's `struct` language
439 # Format of an index entry according to Python's `struct` language
438 index_format = revlog_constants.INDEX_ENTRY_V1
440 index_format = revlog_constants.INDEX_ENTRY_V1
439 # Size of a C unsigned long long int, platform independent
441 # Size of a C unsigned long long int, platform independent
440 big_int_size = struct.calcsize(b'>Q')
442 big_int_size = struct.calcsize(b'>Q')
441 # Size of a C long int, platform independent
443 # Size of a C long int, platform independent
442 int_size = struct.calcsize(b'>i')
444 int_size = struct.calcsize(b'>i')
443 # An empty index entry, used as a default value to be overridden, or nullrev
445 # An empty index entry, used as a default value to be overridden, or nullrev
444 null_item = (
446 null_item = (
445 0,
447 0,
446 0,
448 0,
447 0,
449 0,
448 -1,
450 -1,
449 -1,
451 -1,
450 -1,
452 -1,
451 -1,
453 -1,
452 sha1nodeconstants.nullid,
454 sha1nodeconstants.nullid,
453 0,
455 0,
454 0,
456 0,
455 revlog_constants.COMP_MODE_INLINE,
457 revlog_constants.COMP_MODE_INLINE,
456 revlog_constants.COMP_MODE_INLINE,
458 revlog_constants.COMP_MODE_INLINE,
457 )
459 )
458
460
459 @util.propertycache
461 @util.propertycache
460 def entry_size(self):
462 def entry_size(self):
461 return self.index_format.size
463 return self.index_format.size
462
464
463 @property
465 @property
464 def nodemap(self):
466 def nodemap(self):
465 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
467 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
466 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
468 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
467 return self._nodemap
469 return self._nodemap
468
470
469 @util.propertycache
471 @util.propertycache
470 def _nodemap(self):
472 def _nodemap(self):
471 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
473 nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev})
472 for r in range(0, len(self)):
474 for r in range(0, len(self)):
473 n = self[r][7]
475 n = self[r][7]
474 nodemap[n] = r
476 nodemap[n] = r
475 return nodemap
477 return nodemap
476
478
477 def has_node(self, node):
479 def has_node(self, node):
478 """return True if the node exist in the index"""
480 """return True if the node exist in the index"""
479 return node in self._nodemap
481 return node in self._nodemap
480
482
481 def rev(self, node):
483 def rev(self, node):
482 """return a revision for a node
484 """return a revision for a node
483
485
484 If the node is unknown, raise a RevlogError"""
486 If the node is unknown, raise a RevlogError"""
485 return self._nodemap[node]
487 return self._nodemap[node]
486
488
487 def get_rev(self, node):
489 def get_rev(self, node):
488 """return a revision for a node
490 """return a revision for a node
489
491
490 If the node is unknown, return None"""
492 If the node is unknown, return None"""
491 return self._nodemap.get(node)
493 return self._nodemap.get(node)
492
494
493 def _stripnodes(self, start):
495 def _stripnodes(self, start):
494 if '_nodemap' in vars(self):
496 if '_nodemap' in vars(self):
495 for r in range(start, len(self)):
497 for r in range(start, len(self)):
496 n = self[r][7]
498 n = self[r][7]
497 del self._nodemap[n]
499 del self._nodemap[n]
498
500
499 def clearcaches(self):
501 def clearcaches(self):
500 self.__dict__.pop('_nodemap', None)
502 self.__dict__.pop('_nodemap', None)
501
503
502 def __len__(self):
504 def __len__(self):
503 return self._lgt + len(self._extra)
505 return self._lgt + len(self._extra)
504
506
505 def append(self, tup):
507 def append(self, tup):
506 if '_nodemap' in vars(self):
508 if '_nodemap' in vars(self):
507 self._nodemap[tup[7]] = len(self)
509 self._nodemap[tup[7]] = len(self)
508 data = self._pack_entry(len(self), tup)
510 data = self._pack_entry(len(self), tup)
509 self._extra.append(data)
511 self._extra.append(data)
510
512
511 def _pack_entry(self, rev, entry):
513 def _pack_entry(self, rev, entry):
512 assert entry[8] == 0
514 assert entry[8] == 0
513 assert entry[9] == 0
515 assert entry[9] == 0
514 return self.index_format.pack(*entry[:8])
516 return self.index_format.pack(*entry[:8])
515
517
516 def _check_index(self, i):
518 def _check_index(self, i):
517 if not isinstance(i, int):
519 if not isinstance(i, int):
518 raise TypeError(b"expecting int indexes")
520 raise TypeError(b"expecting int indexes")
519 if i < 0 or i >= len(self):
521 if i < 0 or i >= len(self):
520 raise IndexError
522 raise IndexError
521
523
522 def __getitem__(self, i):
524 def __getitem__(self, i):
523 if i == -1:
525 if i == -1:
524 return self.null_item
526 return self.null_item
525 self._check_index(i)
527 self._check_index(i)
526 if i >= self._lgt:
528 if i >= self._lgt:
527 data = self._extra[i - self._lgt]
529 data = self._extra[i - self._lgt]
528 else:
530 else:
529 index = self._calculate_index(i)
531 index = self._calculate_index(i)
530 data = self._data[index : index + self.entry_size]
532 data = self._data[index : index + self.entry_size]
531 r = self._unpack_entry(i, data)
533 r = self._unpack_entry(i, data)
532 if self._lgt and i == 0:
534 if self._lgt and i == 0:
533 offset = revlogutils.offset_type(0, gettype(r[0]))
535 offset = revlogutils.offset_type(0, gettype(r[0]))
534 r = (offset,) + r[1:]
536 r = (offset,) + r[1:]
535 return r
537 return r
536
538
537 def _unpack_entry(self, rev, data):
539 def _unpack_entry(self, rev, data):
538 r = self.index_format.unpack(data)
540 r = self.index_format.unpack(data)
539 r = r + (
541 r = r + (
540 0,
542 0,
541 0,
543 0,
542 revlog_constants.COMP_MODE_INLINE,
544 revlog_constants.COMP_MODE_INLINE,
543 revlog_constants.COMP_MODE_INLINE,
545 revlog_constants.COMP_MODE_INLINE,
544 )
546 )
545 return r
547 return r
546
548
547 def pack_header(self, header):
549 def pack_header(self, header):
548 """pack header information as binary"""
550 """pack header information as binary"""
549 v_fmt = revlog_constants.INDEX_HEADER
551 v_fmt = revlog_constants.INDEX_HEADER
550 return v_fmt.pack(header)
552 return v_fmt.pack(header)
551
553
552 def entry_binary(self, rev):
554 def entry_binary(self, rev):
553 """return the raw binary string representing a revision"""
555 """return the raw binary string representing a revision"""
554 entry = self[rev]
556 entry = self[rev]
555 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
557 p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8])
556 if rev == 0:
558 if rev == 0:
557 p = p[revlog_constants.INDEX_HEADER.size :]
559 p = p[revlog_constants.INDEX_HEADER.size :]
558 return p
560 return p
559
561
560
562
561 class IndexObject(BaseIndexObject):
563 class IndexObject(BaseIndexObject):
562 def __init__(self, data):
564 def __init__(self, data):
563 assert len(data) % self.entry_size == 0, (
565 assert len(data) % self.entry_size == 0, (
564 len(data),
566 len(data),
565 self.entry_size,
567 self.entry_size,
566 len(data) % self.entry_size,
568 len(data) % self.entry_size,
567 )
569 )
568 self._data = data
570 self._data = data
569 self._lgt = len(data) // self.entry_size
571 self._lgt = len(data) // self.entry_size
570 self._extra = []
572 self._extra = []
571
573
572 def _calculate_index(self, i):
574 def _calculate_index(self, i):
573 return i * self.entry_size
575 return i * self.entry_size
574
576
575 def __delitem__(self, i):
577 def __delitem__(self, i):
576 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
578 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
577 raise ValueError(b"deleting slices only supports a:-1 with step 1")
579 raise ValueError(b"deleting slices only supports a:-1 with step 1")
578 i = i.start
580 i = i.start
579 self._check_index(i)
581 self._check_index(i)
580 self._stripnodes(i)
582 self._stripnodes(i)
581 if i < self._lgt:
583 if i < self._lgt:
582 self._data = self._data[: i * self.entry_size]
584 self._data = self._data[: i * self.entry_size]
583 self._lgt = i
585 self._lgt = i
584 self._extra = []
586 self._extra = []
585 else:
587 else:
586 self._extra = self._extra[: i - self._lgt]
588 self._extra = self._extra[: i - self._lgt]
587
589
588
590
589 class PersistentNodeMapIndexObject(IndexObject):
591 class PersistentNodeMapIndexObject(IndexObject):
590 """a Debug oriented class to test persistent nodemap
592 """a Debug oriented class to test persistent nodemap
591
593
592 We need a simple python object to test API and higher level behavior. See
594 We need a simple python object to test API and higher level behavior. See
593 the Rust implementation for more serious usage. This should be used only
595 the Rust implementation for more serious usage. This should be used only
594 through the dedicated `devel.persistent-nodemap` config.
596 through the dedicated `devel.persistent-nodemap` config.
595 """
597 """
596
598
597 def nodemap_data_all(self):
599 def nodemap_data_all(self):
598 """Return bytes containing a full serialization of a nodemap
600 """Return bytes containing a full serialization of a nodemap
599
601
600 The nodemap should be valid for the full set of revisions in the
602 The nodemap should be valid for the full set of revisions in the
601 index."""
603 index."""
602 return nodemaputil.persistent_data(self)
604 return nodemaputil.persistent_data(self)
603
605
604 def nodemap_data_incremental(self):
606 def nodemap_data_incremental(self):
605 """Return bytes containing a incremental update to persistent nodemap
607 """Return bytes containing a incremental update to persistent nodemap
606
608
607 This containst the data for an append-only update of the data provided
609 This containst the data for an append-only update of the data provided
608 in the last call to `update_nodemap_data`.
610 in the last call to `update_nodemap_data`.
609 """
611 """
610 if self._nm_root is None:
612 if self._nm_root is None:
611 return None
613 return None
612 docket = self._nm_docket
614 docket = self._nm_docket
613 changed, data = nodemaputil.update_persistent_data(
615 changed, data = nodemaputil.update_persistent_data(
614 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
616 self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev
615 )
617 )
616
618
617 self._nm_root = self._nm_max_idx = self._nm_docket = None
619 self._nm_root = self._nm_max_idx = self._nm_docket = None
618 return docket, changed, data
620 return docket, changed, data
619
621
620 def update_nodemap_data(self, docket, nm_data):
622 def update_nodemap_data(self, docket, nm_data):
621 """provide full block of persisted binary data for a nodemap
623 """provide full block of persisted binary data for a nodemap
622
624
623 The data are expected to come from disk. See `nodemap_data_all` for a
625 The data are expected to come from disk. See `nodemap_data_all` for a
624 produceur of such data."""
626 produceur of such data."""
625 if nm_data is not None:
627 if nm_data is not None:
626 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
628 self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data)
627 if self._nm_root:
629 if self._nm_root:
628 self._nm_docket = docket
630 self._nm_docket = docket
629 else:
631 else:
630 self._nm_root = self._nm_max_idx = self._nm_docket = None
632 self._nm_root = self._nm_max_idx = self._nm_docket = None
631
633
632
634
633 class InlinedIndexObject(BaseIndexObject):
635 class InlinedIndexObject(BaseIndexObject):
634 def __init__(self, data, inline=0):
636 def __init__(self, data, inline=0):
635 self._data = data
637 self._data = data
636 self._lgt = self._inline_scan(None)
638 self._lgt = self._inline_scan(None)
637 self._inline_scan(self._lgt)
639 self._inline_scan(self._lgt)
638 self._extra = []
640 self._extra = []
639
641
640 def _inline_scan(self, lgt):
642 def _inline_scan(self, lgt):
641 off = 0
643 off = 0
642 if lgt is not None:
644 if lgt is not None:
643 self._offsets = [0] * lgt
645 self._offsets = [0] * lgt
644 count = 0
646 count = 0
645 while off <= len(self._data) - self.entry_size:
647 while off <= len(self._data) - self.entry_size:
646 start = off + self.big_int_size
648 start = off + self.big_int_size
647 (s,) = struct.unpack(
649 (s,) = struct.unpack(
648 b'>i',
650 b'>i',
649 self._data[start : start + self.int_size],
651 self._data[start : start + self.int_size],
650 )
652 )
651 if lgt is not None:
653 if lgt is not None:
652 self._offsets[count] = off
654 self._offsets[count] = off
653 count += 1
655 count += 1
654 off += self.entry_size + s
656 off += self.entry_size + s
655 if off != len(self._data):
657 if off != len(self._data):
656 raise ValueError(b"corrupted data")
658 raise ValueError(b"corrupted data")
657 return count
659 return count
658
660
659 def __delitem__(self, i):
661 def __delitem__(self, i):
660 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
662 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
661 raise ValueError(b"deleting slices only supports a:-1 with step 1")
663 raise ValueError(b"deleting slices only supports a:-1 with step 1")
662 i = i.start
664 i = i.start
663 self._check_index(i)
665 self._check_index(i)
664 self._stripnodes(i)
666 self._stripnodes(i)
665 if i < self._lgt:
667 if i < self._lgt:
666 self._offsets = self._offsets[:i]
668 self._offsets = self._offsets[:i]
667 self._lgt = i
669 self._lgt = i
668 self._extra = []
670 self._extra = []
669 else:
671 else:
670 self._extra = self._extra[: i - self._lgt]
672 self._extra = self._extra[: i - self._lgt]
671
673
672 def _calculate_index(self, i):
674 def _calculate_index(self, i):
673 return self._offsets[i]
675 return self._offsets[i]
674
676
675
677
676 def parse_index2(data, inline, revlogv2=False):
678 def parse_index2(data, inline, revlogv2=False):
677 if not inline:
679 if not inline:
678 cls = IndexObject2 if revlogv2 else IndexObject
680 cls = IndexObject2 if revlogv2 else IndexObject
679 return cls(data), None
681 return cls(data), None
680 cls = InlinedIndexObject
682 cls = InlinedIndexObject
681 return cls(data, inline), (0, data)
683 return cls(data, inline), (0, data)
682
684
683
685
684 def parse_index_cl_v2(data):
686 def parse_index_cl_v2(data):
685 return IndexChangelogV2(data), None
687 return IndexChangelogV2(data), None
686
688
687
689
688 class IndexObject2(IndexObject):
690 class IndexObject2(IndexObject):
689 index_format = revlog_constants.INDEX_ENTRY_V2
691 index_format = revlog_constants.INDEX_ENTRY_V2
690
692
691 def replace_sidedata_info(
693 def replace_sidedata_info(
692 self,
694 self,
693 rev,
695 rev,
694 sidedata_offset,
696 sidedata_offset,
695 sidedata_length,
697 sidedata_length,
696 offset_flags,
698 offset_flags,
697 compression_mode,
699 compression_mode,
698 ):
700 ):
699 """
701 """
700 Replace an existing index entry's sidedata offset and length with new
702 Replace an existing index entry's sidedata offset and length with new
701 ones.
703 ones.
702 This cannot be used outside of the context of sidedata rewriting,
704 This cannot be used outside of the context of sidedata rewriting,
703 inside the transaction that creates the revision `rev`.
705 inside the transaction that creates the revision `rev`.
704 """
706 """
705 if rev < 0:
707 if rev < 0:
706 raise KeyError
708 raise KeyError
707 self._check_index(rev)
709 self._check_index(rev)
708 if rev < self._lgt:
710 if rev < self._lgt:
709 msg = b"cannot rewrite entries outside of this transaction"
711 msg = b"cannot rewrite entries outside of this transaction"
710 raise KeyError(msg)
712 raise KeyError(msg)
711 else:
713 else:
712 entry = list(self[rev])
714 entry = list(self[rev])
713 entry[0] = offset_flags
715 entry[0] = offset_flags
714 entry[8] = sidedata_offset
716 entry[8] = sidedata_offset
715 entry[9] = sidedata_length
717 entry[9] = sidedata_length
716 entry[11] = compression_mode
718 entry[11] = compression_mode
717 entry = tuple(entry)
719 entry = tuple(entry)
718 new = self._pack_entry(rev, entry)
720 new = self._pack_entry(rev, entry)
719 self._extra[rev - self._lgt] = new
721 self._extra[rev - self._lgt] = new
720
722
721 def _unpack_entry(self, rev, data):
723 def _unpack_entry(self, rev, data):
722 data = self.index_format.unpack(data)
724 data = self.index_format.unpack(data)
723 entry = data[:10]
725 entry = data[:10]
724 data_comp = data[10] & 3
726 data_comp = data[10] & 3
725 sidedata_comp = (data[10] & (3 << 2)) >> 2
727 sidedata_comp = (data[10] & (3 << 2)) >> 2
726 return entry + (data_comp, sidedata_comp)
728 return entry + (data_comp, sidedata_comp)
727
729
728 def _pack_entry(self, rev, entry):
730 def _pack_entry(self, rev, entry):
729 data = entry[:10]
731 data = entry[:10]
730 data_comp = entry[10] & 3
732 data_comp = entry[10] & 3
731 sidedata_comp = (entry[11] & 3) << 2
733 sidedata_comp = (entry[11] & 3) << 2
732 data += (data_comp | sidedata_comp,)
734 data += (data_comp | sidedata_comp,)
733
735
734 return self.index_format.pack(*data)
736 return self.index_format.pack(*data)
735
737
736 def entry_binary(self, rev):
738 def entry_binary(self, rev):
737 """return the raw binary string representing a revision"""
739 """return the raw binary string representing a revision"""
738 entry = self[rev]
740 entry = self[rev]
739 return self._pack_entry(rev, entry)
741 return self._pack_entry(rev, entry)
740
742
741 def pack_header(self, header):
743 def pack_header(self, header):
742 """pack header information as binary"""
744 """pack header information as binary"""
743 msg = 'version header should go in the docket, not the index: %d'
745 msg = 'version header should go in the docket, not the index: %d'
744 msg %= header
746 msg %= header
745 raise error.ProgrammingError(msg)
747 raise error.ProgrammingError(msg)
746
748
747
749
748 class IndexChangelogV2(IndexObject2):
750 class IndexChangelogV2(IndexObject2):
749 index_format = revlog_constants.INDEX_ENTRY_CL_V2
751 index_format = revlog_constants.INDEX_ENTRY_CL_V2
750
752
751 def _unpack_entry(self, rev, data, r=True):
753 def _unpack_entry(self, rev, data, r=True):
752 items = self.index_format.unpack(data)
754 items = self.index_format.unpack(data)
753 entry = items[:3] + (rev, rev) + items[3:8]
755 entry = items[:3] + (rev, rev) + items[3:8]
754 data_comp = items[8] & 3
756 data_comp = items[8] & 3
755 sidedata_comp = (items[8] >> 2) & 3
757 sidedata_comp = (items[8] >> 2) & 3
756 return entry + (data_comp, sidedata_comp)
758 return entry + (data_comp, sidedata_comp)
757
759
758 def _pack_entry(self, rev, entry):
760 def _pack_entry(self, rev, entry):
759 assert entry[3] == rev, entry[3]
761 assert entry[3] == rev, entry[3]
760 assert entry[4] == rev, entry[4]
762 assert entry[4] == rev, entry[4]
761 data = entry[:3] + entry[5:10]
763 data = entry[:3] + entry[5:10]
762 data_comp = entry[10] & 3
764 data_comp = entry[10] & 3
763 sidedata_comp = (entry[11] & 3) << 2
765 sidedata_comp = (entry[11] & 3) << 2
764 data += (data_comp | sidedata_comp,)
766 data += (data_comp | sidedata_comp,)
765 return self.index_format.pack(*data)
767 return self.index_format.pack(*data)
766
768
767
769
768 def parse_index_devel_nodemap(data, inline):
770 def parse_index_devel_nodemap(data, inline):
769 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
771 """like parse_index2, but alway return a PersistentNodeMapIndexObject"""
770 return PersistentNodeMapIndexObject(data), None
772 return PersistentNodeMapIndexObject(data), None
771
773
772
774
773 def parse_dirstate(dmap, copymap, st):
775 def parse_dirstate(dmap, copymap, st):
774 parents = [st[:20], st[20:40]]
776 parents = [st[:20], st[20:40]]
775 # dereference fields so they will be local in loop
777 # dereference fields so they will be local in loop
776 format = b">cllll"
778 format = b">cllll"
777 e_size = struct.calcsize(format)
779 e_size = struct.calcsize(format)
778 pos1 = 40
780 pos1 = 40
779 l = len(st)
781 l = len(st)
780
782
781 # the inner loop
783 # the inner loop
782 while pos1 < l:
784 while pos1 < l:
783 pos2 = pos1 + e_size
785 pos2 = pos1 + e_size
784 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
786 e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster
785 pos1 = pos2 + e[4]
787 pos1 = pos2 + e[4]
786 f = st[pos2:pos1]
788 f = st[pos2:pos1]
787 if b'\0' in f:
789 if b'\0' in f:
788 f, c = f.split(b'\0')
790 f, c = f.split(b'\0')
789 copymap[f] = c
791 copymap[f] = c
790 dmap[f] = DirstateItem.from_v1_data(*e[:4])
792 dmap[f] = DirstateItem.from_v1_data(*e[:4])
791 return parents
793 return parents
792
794
793
795
794 def pack_dirstate(dmap, copymap, pl, now):
796 def pack_dirstate(dmap, copymap, pl, now):
795 now = int(now)
797 now = int(now)
796 cs = stringio()
798 cs = stringio()
797 write = cs.write
799 write = cs.write
798 write(b"".join(pl))
800 write(b"".join(pl))
799 for f, e in pycompat.iteritems(dmap):
801 for f, e in pycompat.iteritems(dmap):
800 if e.need_delay(now):
802 if e.need_delay(now):
801 # The file was last modified "simultaneously" with the current
803 # The file was last modified "simultaneously" with the current
802 # write to dirstate (i.e. within the same second for file-
804 # write to dirstate (i.e. within the same second for file-
803 # systems with a granularity of 1 sec). This commonly happens
805 # systems with a granularity of 1 sec). This commonly happens
804 # for at least a couple of files on 'update'.
806 # for at least a couple of files on 'update'.
805 # The user could change the file without changing its size
807 # The user could change the file without changing its size
806 # within the same second. Invalidate the file's mtime in
808 # within the same second. Invalidate the file's mtime in
807 # dirstate, forcing future 'status' calls to compare the
809 # dirstate, forcing future 'status' calls to compare the
808 # contents of the file if the size is the same. This prevents
810 # contents of the file if the size is the same. This prevents
809 # mistakenly treating such files as clean.
811 # mistakenly treating such files as clean.
810 e.set_possibly_dirty()
812 e.set_possibly_dirty()
811
813
812 if f in copymap:
814 if f in copymap:
813 f = b"%s\0%s" % (f, copymap[f])
815 f = b"%s\0%s" % (f, copymap[f])
814 e = _pack(
816 e = _pack(
815 b">cllll",
817 b">cllll",
816 e.v1_state(),
818 e.v1_state(),
817 e.v1_mode(),
819 e.v1_mode(),
818 e.v1_size(),
820 e.v1_size(),
819 e.v1_mtime(),
821 e.v1_mtime(),
820 len(f),
822 len(f),
821 )
823 )
822 write(e)
824 write(e)
823 write(f)
825 write(f)
824 return cs.getvalue()
826 return cs.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now