comparison mercurial/changelog.py @ 51103:d83d788590a8

changelog-delay: move the delay/divert logic inside the (inner) revlog Instead of hacking throught the vfs/opener, we implement the delay/divert logic inside the `_InnerRevlog` and `randomaccessfile` object. This will allow to an alternative implementation of the `_InnerRevlog` that does not need to use Python details. As a result, the new implementation can use the transaction less agressively and avoid some extra output since no data had been written yet. That seems like a good side effect.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 24 Oct 2023 11:08:49 +0200
parents 1c0f3994d733
children dcaa2df1f688
comparison
equal deleted inserted replaced
51102:af96fbb8f739 51103:d83d788590a8
25 stringutil, 25 stringutil,
26 ) 26 )
27 from .revlogutils import ( 27 from .revlogutils import (
28 constants as revlog_constants, 28 constants as revlog_constants,
29 flagutil, 29 flagutil,
30 randomaccessfile,
31 ) 30 )
32 31
33 _defaultextra = {b'branch': b'default'} 32 _defaultextra = {b'branch': b'default'}
34 33
35 34
90 def stripdesc(desc): 89 def stripdesc(desc):
91 """strip trailing whitespace and leading and trailing empty lines""" 90 """strip trailing whitespace and leading and trailing empty lines"""
92 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n') 91 return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
93 92
94 93
95 class _divertopener:
96 def __init__(self, opener, target):
97 self._opener = opener
98 self._target = target
99
100 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
101 if name != self._target:
102 return self._opener(name, mode, **kwargs)
103 return self._opener(name + b".a", mode, **kwargs)
104
105 def __getattr__(self, attr):
106 return getattr(self._opener, attr)
107
108
109 class _delayopener:
110 """build an opener that stores chunks in 'buf' instead of 'target'"""
111
112 def __init__(self, opener, target, buf):
113 self._opener = opener
114 self._target = target
115 self._buf = buf
116
117 def __call__(self, name, mode=b'r', checkambig=False, **kwargs):
118 if name != self._target:
119 return self._opener(name, mode, **kwargs)
120 assert not kwargs
121 return randomaccessfile.appender(self._opener, name, mode, self._buf)
122
123 def __getattr__(self, attr):
124 return getattr(self._opener, attr)
125
126
127 @attr.s 94 @attr.s
128 class _changelogrevision: 95 class _changelogrevision:
129 # Extensions might modify _defaultextra, so let the constructor below pass 96 # Extensions might modify _defaultextra, so let the constructor below pass
130 # it in 97 # it in
131 extra = attr.ib() 98 extra = attr.ib()
352 # Delta chains for changelogs tend to be very small because entries 319 # Delta chains for changelogs tend to be very small because entries
353 # tend to be small and don't delta well with each. So disable delta 320 # tend to be small and don't delta well with each. So disable delta
354 # chains. 321 # chains.
355 self._storedeltachains = False 322 self._storedeltachains = False
356 323
357 self._realopener = opener 324 self._v2_delayed = False
358 self._delayed = False
359 self._delaybuf = None
360 self._divert = False
361 self._filteredrevs = frozenset() 325 self._filteredrevs = frozenset()
362 self._filteredrevs_hashcache = {} 326 self._filteredrevs_hashcache = {}
363 self._copiesstorage = opener.options.get(b'copies-storage') 327 self._copiesstorage = opener.options.get(b'copies-storage')
364 328
365 @property 329 @property
372 assert isinstance(val, frozenset) 336 assert isinstance(val, frozenset)
373 self._filteredrevs = val 337 self._filteredrevs = val
374 self._filteredrevs_hashcache = {} 338 self._filteredrevs_hashcache = {}
375 339
376 def _write_docket(self, tr): 340 def _write_docket(self, tr):
377 if not self.is_delaying: 341 if not self._v2_delayed:
378 super(changelog, self)._write_docket(tr) 342 super(changelog, self)._write_docket(tr)
379
380 @property
381 def is_delaying(self):
382 return self._delayed
383 343
384 def delayupdate(self, tr): 344 def delayupdate(self, tr):
385 """delay visibility of index updates to other readers""" 345 """delay visibility of index updates to other readers"""
386 assert not self._inner.is_open 346 assert not self._inner.is_open
387 if self._docket is None and not self.is_delaying: 347 if self._docket is not None:
388 if len(self) == 0: 348 self._v2_delayed = True
389 self._divert = True 349 else:
390 if self._realopener.exists(self._indexfile + b'.a'): 350 new_index = self._inner.delay()
391 self._realopener.unlink(self._indexfile + b'.a') 351 if new_index is not None:
392 self.opener = _divertopener(self._realopener, self._indexfile) 352 self._indexfile = new_index
393 else: 353 tr.registertmp(new_index)
394 self._delaybuf = []
395 self.opener = _delayopener(
396 self._realopener, self._indexfile, self._delaybuf
397 )
398 self._inner.opener = self.opener
399 self._inner._segmentfile.opener = self.opener
400 self._inner._segmentfile_sidedata.opener = self.opener
401 self._delayed = True
402 tr.addpending(b'cl-%i' % id(self), self._writepending) 354 tr.addpending(b'cl-%i' % id(self), self._writepending)
403 tr.addfinalize(b'cl-%i' % id(self), self._finalize) 355 tr.addfinalize(b'cl-%i' % id(self), self._finalize)
404 356
405 def _finalize(self, tr): 357 def _finalize(self, tr):
406 """finalize index updates""" 358 """finalize index updates"""
407 assert not self._inner.is_open 359 assert not self._inner.is_open
408 self._delayed = False
409 self.opener = self._realopener
410 self._inner.opener = self.opener
411 self._inner._segmentfile.opener = self.opener
412 self._inner._segmentfile_sidedata.opener = self.opener
413 # move redirected index data back into place
414 if self._docket is not None: 360 if self._docket is not None:
415 self._write_docket(tr) 361 self._docket.write(tr)
416 elif self._divert: 362 self._v2_delayed = False
417 assert not self._delaybuf 363 else:
418 tmpname = self._indexfile + b".a" 364 new_index_file = self._inner.finalize_pending()
419 nfile = self.opener.open(tmpname) 365 self._indexfile = new_index_file
420 nfile.close() 366 # split when we're done
421 self.opener.rename(tmpname, self._indexfile, checkambig=True) 367 self._enforceinlinesize(tr, side_write=False)
422 elif self._delaybuf:
423 fp = self.opener(self._indexfile, b'a', checkambig=True)
424 fp.write(b"".join(self._delaybuf))
425 fp.close()
426 self._delaybuf = None
427 self._divert = False
428 # split when we're done
429 self._enforceinlinesize(tr, side_write=False)
430 368
431 def _writepending(self, tr): 369 def _writepending(self, tr):
432 """create a file containing the unfinalized state for 370 """create a file containing the unfinalized state for
433 pretxnchangegroup""" 371 pretxnchangegroup"""
434 assert not self._inner.is_open 372 assert not self._inner.is_open
435 if self._docket: 373 if self._docket:
436 return self._docket.write(tr, pending=True) 374 any_pending = self._docket.write(tr, pending=True)
437 if self._delaybuf: 375 self._v2_delayed = False
438 # make a temporary copy of the index 376 else:
439 fp1 = self._realopener(self._indexfile) 377 new_index, any_pending = self._inner.write_pending()
440 pendingfilename = self._indexfile + b".a" 378 if new_index is not None:
441 # register as a temp file to ensure cleanup on failure 379 self._indexfile = new_index
442 tr.registertmp(pendingfilename) 380 tr.registertmp(new_index)
443 # write existing data 381 return any_pending
444 fp2 = self._realopener(pendingfilename, b"w")
445 fp2.write(fp1.read())
446 # add pending data
447 fp2.write(b"".join(self._delaybuf))
448 fp2.close()
449 # switch modes so finalize can simply rename
450 self._delaybuf = None
451 self._divert = True
452 self.opener = _divertopener(self._realopener, self._indexfile)
453 self._inner.opener = self.opener
454 self._inner._segmentfile.opener = self.opener
455 self._inner._segmentfile_sidedata.opener = self.opener
456
457 if self._divert:
458 return True
459
460 return False
461 382
462 def _enforceinlinesize(self, tr, side_write=True): 383 def _enforceinlinesize(self, tr, side_write=True):
463 if not self.is_delaying: 384 if not self.is_delaying:
464 revlog.revlog._enforceinlinesize(self, tr, side_write=side_write) 385 revlog.revlog._enforceinlinesize(self, tr, side_write=side_write)
465 386