comparison mercurial/pure/parsers.py @ 48260:269ff8978086

dirstate: store mtimes with nanosecond precision in memory Keep integer seconds since the Unix epoch, together with integer nanoseconds in the `0 <= n < 1e9` range. For now, nanoseconds are still always zero. This commit is about data structure changes. Differential Revision: https://phab.mercurial-scm.org/D11684
author Simon Sapin <simon.sapin@octobus.net>
date Mon, 18 Oct 2021 11:23:07 +0200
parents b874e8d81a98
children 9205d9be8b41
comparison
equal deleted inserted replaced
48259:84f6b0c41b90 48260:269ff8978086
97 _wc_tracked = attr.ib() 97 _wc_tracked = attr.ib()
98 _p1_tracked = attr.ib() 98 _p1_tracked = attr.ib()
99 _p2_info = attr.ib() 99 _p2_info = attr.ib()
100 _mode = attr.ib() 100 _mode = attr.ib()
101 _size = attr.ib() 101 _size = attr.ib()
102 _mtime = attr.ib() 102 _mtime_s = attr.ib()
103 _mtime_ns = attr.ib()
103 _fallback_exec = attr.ib() 104 _fallback_exec = attr.ib()
104 _fallback_symlink = attr.ib() 105 _fallback_symlink = attr.ib()
105 106
106 def __init__( 107 def __init__(
107 self, 108 self,
121 self._fallback_exec = fallback_exec 122 self._fallback_exec = fallback_exec
122 self._fallback_symlink = fallback_symlink 123 self._fallback_symlink = fallback_symlink
123 124
124 self._mode = None 125 self._mode = None
125 self._size = None 126 self._size = None
126 self._mtime = None 127 self._mtime_s = None
128 self._mtime_ns = None
127 if parentfiledata is None: 129 if parentfiledata is None:
128 has_meaningful_mtime = False 130 has_meaningful_mtime = False
129 has_meaningful_data = False 131 has_meaningful_data = False
130 if has_meaningful_data: 132 if has_meaningful_data:
131 self._mode = parentfiledata[0] 133 self._mode = parentfiledata[0]
132 self._size = parentfiledata[1] 134 self._size = parentfiledata[1]
133 if has_meaningful_mtime: 135 if has_meaningful_mtime:
134 self._mtime = parentfiledata[2] 136 self._mtime_s, self._mtime_ns = parentfiledata[2]
135 137
136 @classmethod 138 @classmethod
137 def from_v2_data(cls, flags, size, mtime): 139 def from_v2_data(cls, flags, size, mtime_s, mtime_ns):
138 """Build a new DirstateItem object from V2 data""" 140 """Build a new DirstateItem object from V2 data"""
139 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) 141 has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE)
140 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_FILE_MTIME) 142 has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_FILE_MTIME)
141 mode = None 143 mode = None
142 144
168 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), 170 wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED),
169 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), 171 p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED),
170 p2_info=bool(flags & DIRSTATE_V2_P2_INFO), 172 p2_info=bool(flags & DIRSTATE_V2_P2_INFO),
171 has_meaningful_data=has_mode_size, 173 has_meaningful_data=has_mode_size,
172 has_meaningful_mtime=has_meaningful_mtime, 174 has_meaningful_mtime=has_meaningful_mtime,
173 parentfiledata=(mode, size, mtime), 175 parentfiledata=(mode, size, (mtime_s, mtime_ns)),
174 fallback_exec=fallback_exec, 176 fallback_exec=fallback_exec,
175 fallback_symlink=fallback_symlink, 177 fallback_symlink=fallback_symlink,
176 ) 178 )
177 179
178 @classmethod 180 @classmethod
205 elif mtime == AMBIGUOUS_TIME: 207 elif mtime == AMBIGUOUS_TIME:
206 return cls( 208 return cls(
207 wc_tracked=True, 209 wc_tracked=True,
208 p1_tracked=True, 210 p1_tracked=True,
209 has_meaningful_mtime=False, 211 has_meaningful_mtime=False,
210 parentfiledata=(mode, size, 42), 212 parentfiledata=(mode, size, (42, 0)),
211 ) 213 )
212 else: 214 else:
213 return cls( 215 return cls(
214 wc_tracked=True, 216 wc_tracked=True,
215 p1_tracked=True, 217 p1_tracked=True,
216 parentfiledata=(mode, size, mtime), 218 parentfiledata=(mode, size, (mtime, 0)),
217 ) 219 )
218 else: 220 else:
219 raise RuntimeError(b'unknown state: %s' % state) 221 raise RuntimeError(b'unknown state: %s' % state)
220 222
221 def set_possibly_dirty(self): 223 def set_possibly_dirty(self):
222 """Mark a file as "possibly dirty" 224 """Mark a file as "possibly dirty"
223 225
224 This means the next status call will have to actually check its content 226 This means the next status call will have to actually check its content
225 to make sure it is correct. 227 to make sure it is correct.
226 """ 228 """
227 self._mtime = None 229 self._mtime_s = None
230 self._mtime_ns = None
228 231
229 def set_clean(self, mode, size, mtime): 232 def set_clean(self, mode, size, mtime):
230 """mark a file as "clean" cancelling potential "possibly dirty call" 233 """mark a file as "clean" cancelling potential "possibly dirty call"
231 234
232 Note: this function is a descendant of `dirstate.normal` and is 235 Note: this function is a descendant of `dirstate.normal` and is
236 """ 239 """
237 self._wc_tracked = True 240 self._wc_tracked = True
238 self._p1_tracked = True 241 self._p1_tracked = True
239 self._mode = mode 242 self._mode = mode
240 self._size = size 243 self._size = size
241 self._mtime = mtime 244 self._mtime_s, self._mtime_ns = mtime
242 245
243 def set_tracked(self): 246 def set_tracked(self):
244 """mark a file as tracked in the working copy 247 """mark a file as tracked in the working copy
245 248
246 This will ultimately be called by command like `hg add`. 249 This will ultimately be called by command like `hg add`.
248 self._wc_tracked = True 251 self._wc_tracked = True
249 # `set_tracked` is replacing various `normallookup` call. So we mark 252 # `set_tracked` is replacing various `normallookup` call. So we mark
250 # the files as needing lookup 253 # the files as needing lookup
251 # 254 #
252 # Consider dropping this in the future in favor of something less broad. 255 # Consider dropping this in the future in favor of something less broad.
253 self._mtime = None 256 self._mtime_s = None
257 self._mtime_ns = None
254 258
255 def set_untracked(self): 259 def set_untracked(self):
256 """mark a file as untracked in the working copy 260 """mark a file as untracked in the working copy
257 261
258 This will ultimately be called by command like `hg remove`. 262 This will ultimately be called by command like `hg remove`.
259 """ 263 """
260 self._wc_tracked = False 264 self._wc_tracked = False
261 self._mode = None 265 self._mode = None
262 self._size = None 266 self._size = None
263 self._mtime = None 267 self._mtime_s = None
268 self._mtime_ns = None
264 269
265 def drop_merge_data(self): 270 def drop_merge_data(self):
266 """remove all "merge-only" from a DirstateItem 271 """remove all "merge-only" from a DirstateItem
267 272
268 This is to be call by the dirstatemap code when the second parent is dropped 273 This is to be call by the dirstatemap code when the second parent is dropped
269 """ 274 """
270 if self._p2_info: 275 if self._p2_info:
271 self._p2_info = False 276 self._p2_info = False
272 self._mode = None 277 self._mode = None
273 self._size = None 278 self._size = None
274 self._mtime = None 279 self._mtime_s = None
280 self._mtime_ns = None
275 281
276 @property 282 @property
277 def mode(self): 283 def mode(self):
278 return self.v1_mode() 284 return self.v1_mode()
279 285
282 return self.v1_size() 288 return self.v1_size()
283 289
284 @property 290 @property
285 def mtime(self): 291 def mtime(self):
286 return self.v1_mtime() 292 return self.v1_mtime()
293
294 def mtime_likely_equal_to(self, other_mtime):
295 self_sec = self._mtime_s
296 if self_sec is None:
297 return False
298 self_ns = self._mtime_ns
299 other_sec, other_ns = other_mtime
300 return self_sec == other_sec and self_ns == other_ns
287 301
288 @property 302 @property
289 def state(self): 303 def state(self):
290 """ 304 """
291 States are: 305 States are:
438 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE 452 flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE
439 if self.mode & stat.S_IXUSR: 453 if self.mode & stat.S_IXUSR:
440 flags |= DIRSTATE_V2_MODE_EXEC_PERM 454 flags |= DIRSTATE_V2_MODE_EXEC_PERM
441 if stat.S_ISLNK(self.mode): 455 if stat.S_ISLNK(self.mode):
442 flags |= DIRSTATE_V2_MODE_IS_SYMLINK 456 flags |= DIRSTATE_V2_MODE_IS_SYMLINK
443 if self._mtime is not None: 457 if self._mtime_s is not None:
444 flags |= DIRSTATE_V2_HAS_FILE_MTIME 458 flags |= DIRSTATE_V2_HAS_FILE_MTIME
445 459
446 if self._fallback_exec is not None: 460 if self._fallback_exec is not None:
447 flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC 461 flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC
448 if self._fallback_exec: 462 if self._fallback_exec:
454 flags |= DIRSTATE_V2_FALLBACK_SYMLINK 468 flags |= DIRSTATE_V2_FALLBACK_SYMLINK
455 469
456 # Note: we do not need to do anything regarding 470 # Note: we do not need to do anything regarding
457 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED 471 # DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED
458 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME 472 # since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME
459 return (flags, self._size or 0, self._mtime or 0) 473 return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0)
460 474
461 def v1_state(self): 475 def v1_state(self):
462 """return a "state" suitable for v1 serialization""" 476 """return a "state" suitable for v1 serialization"""
463 if not self.any_tracked: 477 if not self.any_tracked:
464 # the object has no state to record, this is -currently- 478 # the object has no state to record, this is -currently-
502 # the object has no state to record, this is -currently- 516 # the object has no state to record, this is -currently-
503 # unsupported 517 # unsupported
504 raise RuntimeError('untracked item') 518 raise RuntimeError('untracked item')
505 elif self.removed: 519 elif self.removed:
506 return 0 520 return 0
507 elif self._mtime is None: 521 elif self._mtime_s is None:
508 return AMBIGUOUS_TIME 522 return AMBIGUOUS_TIME
509 elif self._p2_info: 523 elif self._p2_info:
510 return AMBIGUOUS_TIME 524 return AMBIGUOUS_TIME
511 elif not self._p1_tracked: 525 elif not self._p1_tracked:
512 return AMBIGUOUS_TIME 526 return AMBIGUOUS_TIME
513 else: 527 else:
514 return self._mtime 528 return self._mtime_s
515 529
516 def need_delay(self, now): 530 def need_delay(self, now):
517 """True if the stored mtime would be ambiguous with the current time""" 531 """True if the stored mtime would be ambiguous with the current time"""
518 return self.v1_state() == b'n' and self.v1_mtime() == now 532 return self.v1_state() == b'n' and self._mtime_s == now[0]
519 533
520 534
521 def gettype(q): 535 def gettype(q):
522 return int(q & 0xFFFF) 536 return int(q & 0xFFFF)
523 537
881 dmap[f] = DirstateItem.from_v1_data(*e[:4]) 895 dmap[f] = DirstateItem.from_v1_data(*e[:4])
882 return parents 896 return parents
883 897
884 898
885 def pack_dirstate(dmap, copymap, pl, now): 899 def pack_dirstate(dmap, copymap, pl, now):
886 now = int(now)
887 cs = stringio() 900 cs = stringio()
888 write = cs.write 901 write = cs.write
889 write(b"".join(pl)) 902 write(b"".join(pl))
890 for f, e in pycompat.iteritems(dmap): 903 for f, e in pycompat.iteritems(dmap):
891 if e.need_delay(now): 904 if e.need_delay(now):