# HG changeset patch # User Martin von Zweigbergk # Date 2017-07-29 05:42:10 # Node ID bbbbd3c30bfcf3e35a0d802e74c0dd2835d5f805 # Parent 5904511fc9f8389d94970cf84200c07ec1187699 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 diff --git a/mercurial/dirstateguard.py b/mercurial/dirstateguard.py --- a/mercurial/dirstateguard.py +++ b/mercurial/dirstateguard.py @@ -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") diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -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 diff --git a/mercurial/transaction.py b/mercurial/transaction.py --- a/mercurial/transaction.py +++ b/mercurial/transaction.py @@ -101,7 +101,7 @@ def _playback(journal, report, opener, v # 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 diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -15,6 +15,7 @@ hide platform-specific details from the 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