diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -86,30 +86,66 @@ _initstdio() req = request(pycompat.sysargv[1:]) err = None + + # The logic here attempts to mimic how CPython's pythonrun.c does things. + # Essentially: + # + # * Exit is controlled by returning a value or raising SystemExit. + # * An integer value exits with that exit code. + # * A value of ``None`` is interpreted as ``0``. + # * A non-integer, non-None value results in that value being printed + # and an exit code of ``1``. + # * SystemExit can have a ``code`` attribute containing the exit value. try: - status = (dispatch(req) or 0) + code = dispatch(req) or 0 except error.StdioError as e: err = e - status = -1 + code = -1 + except SystemExit as err: + try: + code = err.code + err = None + except AttributeError: + pass + + # In all cases we flush our stdio streams. if util.safehasattr(req.ui, 'fout'): try: req.ui.fout.flush() except IOError as e: err = e - status = -1 + code = -1 + if util.safehasattr(req.ui, 'ferr'): try: if err is not None and err.errno != errno.EPIPE: req.ui.ferr.write('abort: %s\n' % encoding.strtolocal(err.strerror)) - req.ui.ferr.flush() # There's not much we can do about an I/O error here. So (possibly) # change the status code and move on. except IOError: - status = -1 + code = -1 - _silencestdio() - sys.exit(status & 255) + # If we got a non-integer exit code, we print that as well. + if not isinstance(code, int) and util.safehasattr(req.ui, 'ferr'): + try: + req.ui.ferr.write('%r\n' % code) + # This will ignore errors formatting the value and errors printing + # to stderr. That's intended. At this point, there's not much + # we can do about any errors. + except Exception: + pass + + code = 1 + + if util.safehasattr(req.ui, 'ferr'): + try: + req.ui.ferr.flush() + except IOError: + # There's not much we can do about I/O errors at this point. + code = -1 + + sys.exit(code & 255) if pycompat.ispy3: def _initstdio(): diff --git a/tests/test-dispatch.t b/tests/test-dispatch.t --- a/tests/test-dispatch.t +++ b/tests/test-dispatch.t @@ -263,12 +263,7 @@ [42] $ hg exit returnstring 'some message' - Traceback (most recent call last): - File "*/hg", line *, in (glob) - dispatch.run() - File "*/mercurial/dispatch.py", line *, in run (glob) - sys.exit(status & 255) - TypeError: unsupported operand type(s) for &: 'str' and 'int' + 'some message' [1] $ hg exit returnnone @@ -276,24 +271,14 @@ $ hg exit returnempty $ hg exit returndict - Traceback (most recent call last): - File "*/hg", line *, in (glob) - dispatch.run() - File "*/mercurial/dispatch.py", line *, in run (glob) - sys.exit(status & 255) - TypeError: unsupported operand type(s) for &: 'dict' and 'int' + {'key1': 'value1'} [1] $ hg exit systemexitint 42 [42] $ hg exit systemexitstring 'failure message' - Traceback (most recent call last): - File "*/hg", line *, in (glob) - dispatch.run() - File "*/mercurial/dispatch.py", line *, in run (glob) - sys.exit(status & 255) - TypeError: unsupported operand type(s) for &: 'str' and 'int' + 'failure message' [1] $ hg exit systemexitnoarg @@ -301,10 +286,5 @@ $ hg exit systemexitnone $ hg exit systemexitdict - Traceback (most recent call last): - File "*/hg", line *, in (glob) - dispatch.run() - File "*/mercurial/dispatch.py", line *, in run (glob) - sys.exit(status & 255) - TypeError: unsupported operand type(s) for &: 'dict' and 'int' + {'key1': 'value1'} [1]