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, ) 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)