Mercurial > public > mercurial-scm > hg
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 |