util: add base class for transactional context managers

We have at least three types with a close() and a release() method
where the close() method is supposed to be called on success and the
release() method is supposed to be called last, whether successful or
not. Two of them (transaction and dirstateguard) already have
identical implementations of __enter__ and __exit__. Let's extract a
base class for this, so we reuse the code and so the third type
(transactionmanager) can also be used as a context manager.

Differential Revision: https://phab.mercurial-scm.org/D392
This commit is contained in:
Martin von Zweigbergk 2017-07-28 22:42:10 -07:00
parent 503fee4003
commit e5ad1ba424
4 changed files with 30 additions and 23 deletions

View File

@ -11,9 +11,10 @@ from .i18n import _
from . import (
error,
util,
)
class dirstateguard(object):
class dirstateguard(util.transactional):
'''Restore dirstate at unexpected failure.
At the construction, this class does:
@ -43,16 +44,6 @@ class dirstateguard(object):
# ``release(tr, ....)``.
self._abort()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is None:
self.close()
finally:
self.release()
def close(self):
if not self._active: # already inactivated
msg = (_("can't close already inactivated backup: %s")

View File

@ -1168,7 +1168,7 @@ class pulloperation(object):
# deprecated; talk to trmanager directly
return self.trmanager.transaction()
class transactionmanager(object):
class transactionmanager(util.transactional):
"""An object to manage the life cycle of a transaction
It creates the transaction on demand and calls the appropriate hooks when

View File

@ -101,7 +101,7 @@ def _playback(journal, report, opener, vfsmap, entries, backupentries,
# only pure backup file remains, it is sage to ignore any error
pass
class transaction(object):
class transaction(util.transactional):
def __init__(self, report, opener, vfsmap, journalname, undoname=None,
after=None, createmode=None, validator=None, releasefn=None,
checkambigfiles=None):
@ -376,16 +376,6 @@ class transaction(object):
if self.count > 0 and self.usages == 0:
self._abort()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is None:
self.close()
finally:
self.release()
def running(self):
return self.count > 0

View File

@ -15,6 +15,7 @@ hide platform-specific details from the core.
from __future__ import absolute_import
import abc
import bz2
import calendar
import codecs
@ -592,6 +593,31 @@ class sortdict(collections.OrderedDict):
for k, v in src:
self[k] = v
class transactional(object):
"""Base class for making a transactional type into a context manager."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def close(self):
"""Successfully closes the transaction."""
@abc.abstractmethod
def release(self):
"""Marks the end of the transaction.
If the transaction has not been closed, it will be aborted.
"""
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
try:
if exc_type is None:
self.close()
finally:
self.release()
@contextlib.contextmanager
def acceptintervention(tr=None):
"""A context manager that closes the transaction on InterventionRequired