D3716: ui: add an unsafeoperation context manager that can block SIGINT

durin42 (Augie Fackler) phabricator at mercurial-scm.org
Tue Jun 12 15:31:53 UTC 2018


durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  The blocking of SIGINT is not done by default, but my hope is that we
  will one day. This was inspired by Facebook's "nointerrupt" extension,
  which is a bit more heavy-handed than this (whole commands are treated
  as unsafe to interrupt). A future patch will enable this for varying
  bits of Mercurial that are performing unsafe operations.
  
  .. api::
  
    New context manager ``ui.unsafeoperation()`` to mark portions of a command
    as potentially unsafe places to interrupt Mercurial with Control-C or
    similar.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D3716

AFFECTED FILES
  mercurial/configitems.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -224,6 +224,7 @@
         self._colormode = None
         self._terminfoparams = {}
         self._styles = {}
+        self._oldsiginthandler = None
 
         if src:
             self.fout = src.fout
@@ -334,6 +335,39 @@
             self._blockedtimes[key + '_blocked'] += \
                 (util.timer() - starttime) * 1000
 
+    @contextlib.contextmanager
+    def unsafeoperation(self):
+        """Mark an operation as unsafe.
+
+        Most operations on a repository are safe to interrupt, but a
+        few are risky (for example repair.strip). This context manager
+        lets you advise Mercurial that something risky is happening so
+        that control-C etc can be blocked if desired.
+        """
+        enabled = self.configbool('experimental', 'nointerrupt')
+        inter = self.interactive() or not self.configbool(
+            'experimental', 'nointerrupt-interactiveonly')
+        if not (enabled and inter and self._oldsiginthandler is None):
+            # if nointerrupt support is turned off, the process isn't
+            # interactive, or we're already in an unsafeoperation
+            # block, do nothing.
+            yield
+            return
+
+        def disablesiginthandler(*args):
+            self.warn(self.config('experimental', 'nointerrupt-message') + '\n')
+            signal.signal(signal.SIGINT, self._oldsiginthandler)
+            self._oldsiginthandler = None
+
+        try:
+            self._oldsiginthandler = signal.getsignal(signal.SIGINT)
+            signal.signal(signal.SIGINT, disablesiginthandler)
+            yield
+        finally:
+            if self._oldsiginthandler is not None:
+                signal.signal(signal.SIGINT, self._oldsiginthandler)
+            self._oldsiginthandler = None
+
     def formatter(self, topic, opts):
         return formatter.formatter(self, self, topic, opts)
 
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -560,6 +560,17 @@
 coreconfigitem('experimental', 'mergedriver',
     default=None,
 )
+coreconfigitem('experimental', 'nointerrupt', default=False)
+_nointmsg = """
+==========================
+Interrupting Mercurial may leave your repo in a bad state.
+If you really want to interrupt your current command, press
+CTRL-C again.
+==========================
+""".strip()
+coreconfigitem('experimental', 'nointerrupt-message', default=_nointmsg)
+coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
+
 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
     default=False,
 )



To: durin42, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list