What is the recommended approach to handling errors in a Mercurial extension? We could not find any docs for this aspect online.
The following example will print Hello world
and set errorlevel
to 1
in a Windows shell.
@command('print-parents', [('','', None, '')], '')
def printparents(ui, repo, node, **opts):
print "Hello world"
return True
Why is errorlevel
set to 1
? We expected it to be 0
. How does Python or Mercurial handle True
and False
in this context? Changing to return False
produces errorlevel
0
.
We have seen some examples of raise error.Abort(..)
but this output a very verbose callstack which is not neccessary. A simple text message and correct errorlevel
is what is needed.
Using Windows 7 and Mercurial 3.4.1
In a @command
function, use ui.warn()
to display an error message, then return an integer value to set the exit code:
if something_is_wrong:
ui.warn(_('Something is wrong, please correct this'))
return 1
However, using raise mercurial.error.Abort()
is fine too and doesn't result in a traceback unless you have ui.traceback
set to True
(it defaults to False
). Raising Abort()
normally results in a ui.warn('abort: ' + msg_from_exception)
and return -1
combo (which results in a 255 exit code):
$ cat demo.py
from mercurial import cmdutil, error
cmdtable = {}
command = cmdutil.command(cmdtable)
@command('demo1', [], '')
def demo1(ui, repo, *args, **opts):
ui.warn('Error message written directly to ui.warn')
return 1
@command('demo2', [], '')
def demo2(ui, repo, *args, **opts):
raise error.Abort('Error message raised with Abort')
$ hg --config extensions.demo=`pwd`/demo.py demo1
Error message written directly to ui.warn
$ echo $?
1
$ hg --config extensions.demo=`pwd`/demo.py demo2
abort: Error message raised with Abort
$?
255
$ hg --config extensions.demo=`pwd`/demo.py --config ui.traceback=true demo2
Traceback (most recent call last):
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 204, in _runcatch
return _dispatch(req)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 880, in _dispatch
cmdpats, cmdoptions)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/extensions.py", line 210, in closure
return func(*(args + a), **kw)
File "/opt/facebook/hg/lib/python2.7/site-packages/fastmanifest/cachemanager.py", line 318, in runcommandtrigger
result = orig(*args, **kwargs)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/extensions.py", line 210, in closure
return func(*(args + a), **kw)
File "/opt/facebook/hg/lib/python2.7/site-packages/fastmanifest/__init__.py", line 174, in _logonexit
r = orig(ui, repo, cmd, fullargs, *args)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/extensions.py", line 210, in closure
return func(*(args + a), **kw)
File "/opt/facebook/hg/lib/python2.7/site-packages/hgext/journal.py", line 79, in runcommand
return orig(lui, repo, cmd, fullargs, *args)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 637, in runcommand
ret = _runcommand(ui, options, cmd, d)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/extensions.py", line 210, in closure
return func(*(args + a), **kw)
File "/opt/facebook/hg/lib/python2.7/site-packages/hgext/pager.py", line 160, in pagecmd
return orig(ui, options, cmd, cmdfunc)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/extensions.py", line 210, in closure
return func(*(args + a), **kw)
File "/opt/facebook/hg/lib/python2.7/site-packages/hgext/color.py", line 503, in colorcmd
return orig(ui_, opts, cmd, cmdfunc)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 1010, in _runcommand
return checkargs()
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 971, in checkargs
return cmdfunc()
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/dispatch.py", line 877, in <lambda>
d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
File "/opt/facebook/hg/lib/python2.7/site-packages/mercurial/util.py", line 1036, in check
return func(*args, **kwargs)
File "/tmp/demo/demo.py", line 13, in demo2
raise error.Abort('Error message raised with Abort')
Abort: Error message raised with Abort
abort: Error message raised with Abort
Note that I had to explicitly enable the traceback!
In Python, the bool
type is a subclass of int
, and True
has a value of 1
and False
has an integer value of 0
. Because Mercurial expects @command
functions to return an integer to set an exit code (or None
for exit code 0), you set the exit code to 1
by returning True
.
I generally look over the mercurial.commands
module and the various hgext
extensions to learn how others have solved specific problems. The hg grep
command, for example, contains this example of handling an error:
try:
regexp = util.re.compile(pattern, reflags)
except re.error as inst:
ui.warn(_("grep: invalid match pattern: %s\n") % inst)
return 1
while the hg addremove
code raises error.Abort()
:
try:
sim = float(opts.get('similarity') or 100)
except ValueError:
raise error.Abort(_('similarity must be a number'))
I'd use error.Abort()
for command errors, and ui.warn()
plus returning an integer only if you specifically need an exit code other than 255.