diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -1457,7 +1457,7 @@ def heads(ui, repo, *branchrevs, **opts) displayer.show(ctx) displayer.close() -def help_(ui, name=None, with_version=False): +def help_(ui, name=None, with_version=False, unknowncmd=False): """show help for a given topic or a help overview With no arguments, print a list of commands with short help messages. @@ -1490,7 +1490,7 @@ def help_(ui, name=None, with_version=Fa ui.write('\n') try: - aliases, entry = cmdutil.findcmd(name, table, False) + aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd) except error.AmbiguousCommand, inst: # py3k fix: except vars can't be used outside the scope of the # except block, nor can be used inside a lambda. python issue4617 @@ -1501,7 +1501,8 @@ def help_(ui, name=None, with_version=Fa # check if it's an invalid alias and display its error if it is if getattr(entry[0], 'badalias', False): - entry[0](ui) + if not unknowncmd: + entry[0](ui) return # synopsis @@ -1592,10 +1593,13 @@ def help_(ui, name=None, with_version=Fa def helpext(name): try: mod = extensions.find(name) + doc = gettext(mod.__doc__) or _('no help text available') except KeyError: - raise error.UnknownCommand(name) - - doc = gettext(mod.__doc__) or _('no help text available') + mod = None + doc = extensions.disabledext(name) + if not doc: + raise error.UnknownCommand(name) + if '\n' not in doc: head, tail = doc, "" else: @@ -1605,17 +1609,36 @@ def help_(ui, name=None, with_version=Fa ui.write(minirst.format(tail, textwidth)) ui.status('\n\n') - try: - ct = mod.cmdtable - except AttributeError: - ct = {} - - modcmds = set([c.split('|', 1)[0] for c in ct]) - helplist(_('list of commands:\n\n'), modcmds.__contains__) + if mod: + try: + ct = mod.cmdtable + except AttributeError: + ct = {} + modcmds = set([c.split('|', 1)[0] for c in ct]) + helplist(_('list of commands:\n\n'), modcmds.__contains__) + else: + ui.write(_('use "hg help extensions" for information on enabling ' + 'extensions\n')) + + def helpextcmd(name): + cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict')) + doc = gettext(mod.__doc__).splitlines()[0] + + msg = help.listexts(_("'%s' is provided by the following " + "extension:") % cmd, {ext: doc}, len(ext), + indent=4) + ui.write(minirst.format(msg, textwidth)) + ui.write('\n\n') + ui.write(_('use "hg help extensions" for information on enabling ' + 'extensions\n')) if name and name != 'shortlist': i = None - for f in (helptopic, helpcmd, helpext): + if unknowncmd: + queries = (helpextcmd,) + else: + queries = (helptopic, helpcmd, helpext, helpextcmd) + for f in queries: try: f(name) i = None diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -93,7 +93,12 @@ def _runcatch(ui, args): ui.warn(_("killed!\n")) except error.UnknownCommand, inst: ui.warn(_("hg: unknown command '%s'\n") % inst.args[0]) - commands.help_(ui, 'shortlist') + try: + # check if the command is in a disabled extension + # (but don't check for extensions themselves) + commands.help_(ui, inst.args[0], unknowncmd=True) + except error.UnknownCommand: + commands.help_(ui, 'shortlist') except util.Abort, inst: ui.warn(_("abort: %s\n") % inst) except ImportError, inst: @@ -218,6 +223,11 @@ class cmdalias(object): def fn(ui, *args): ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \ % (self.name, cmd)) + try: + # check if the command is in a disabled extension + commands.help_(ui, cmd, unknowncmd=True) + except error.UnknownCommand: + pass return 1 self.fn = fn self.badalias = True diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- a/mercurial/extensions.py +++ b/mercurial/extensions.py @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. import imp, os -import util, cmdutil, help +import util, cmdutil, help, error from i18n import _, gettext _extensions = {} @@ -131,8 +131,9 @@ def wrapfunction(container, funcname, wr setattr(container, funcname, wrap) return origfn -def _disabledpaths(): - '''find paths of disabled extensions. returns a dict of {name: path}''' +def _disabledpaths(strip_init=False): + '''find paths of disabled extensions. returns a dict of {name: path} + removes /__init__.py from packages if strip_init is True''' import hgext extpath = os.path.dirname(os.path.abspath(hgext.__file__)) try: # might not be a filesystem path @@ -150,6 +151,8 @@ def _disabledpaths(): path = os.path.join(extpath, e, '__init__.py') if not os.path.exists(path): continue + if strip_init: + path = os.path.dirname(path) if name in exts or name in _order or name == '__init__': continue exts[name] = path @@ -191,6 +194,53 @@ def disabled(): return exts, maxlength +def disabledext(name): + '''find a specific disabled extension from hgext. returns desc''' + paths = _disabledpaths() + if name in paths: + return _disabledhelp(paths[name]) + +def disabledcmd(cmd, strict=False): + '''import disabled extensions until cmd is found. + returns (cmdname, extname, doc)''' + + paths = _disabledpaths(strip_init=True) + if not paths: + raise error.UnknownCommand(cmd) + + def findcmd(cmd, name, path): + try: + mod = loadpath(path, 'hgext.%s' % name) + except Exception: + return + try: + aliases, entry = cmdutil.findcmd(cmd, + getattr(mod, 'cmdtable', {}), strict) + except (error.AmbiguousCommand, error.UnknownCommand): + return + for c in aliases: + if c.startswith(cmd): + cmd = c + break + else: + cmd = aliases[0] + return (cmd, name, mod) + + # first, search for an extension with the same name as the command + path = paths.pop(cmd, None) + if path: + ext = findcmd(cmd, cmd, path) + if ext: + return ext + + # otherwise, interrogate each extension until there's a match + for name, path in paths.iteritems(): + ext = findcmd(cmd, name, path) + if ext: + return ext + + raise error.UnknownCommand(cmd) + def enabled(): '''return a dict of {name: desc} of extensions, and the max name length''' exts = {} diff --git a/mercurial/help.py b/mercurial/help.py --- a/mercurial/help.py +++ b/mercurial/help.py @@ -42,13 +42,14 @@ def moduledoc(file): return ''.join(result) -def listexts(header, exts, maxlength): +def listexts(header, exts, maxlength, indent=1): '''return a text listing of the given extensions''' if not exts: return '' result = '\n%s\n\n' % header for name, desc in sorted(exts.iteritems()): - result += ' %-*s %s\n' % (maxlength + 2, ':%s:' % name, desc) + result += '%s%-*s %s\n' % (' ' * indent, maxlength + 2, + ':%s:' % name, desc) return result def extshelp(): diff --git a/tests/test-extension b/tests/test-extension --- a/tests/test-extension +++ b/tests/test-extension @@ -153,3 +153,27 @@ echo "hgext/mq=" >> $HGRCPATH echo % show extensions hg debugextensions + +echo '% disabled extension commands' +HGRCPATH= +hg help email +hg qdel +hg churn +echo '% disabled extensions' +hg help churn +hg help patchbomb +echo '% broken disabled extension and command' +mkdir hgext +echo > hgext/__init__.py +cat > hgext/broken.py < /dev/null +PYTHONPATH="$TMPPYTHONPATH" +export PYTHONPATH + +exit 0 diff --git a/tests/test-extension.out b/tests/test-extension.out --- a/tests/test-extension.out +++ b/tests/test-extension.out @@ -96,3 +96,33 @@ global options: % show extensions debugissue811 mq +% disabled extension commands +'email' is provided by the following extension: + + patchbomb command to send changesets as (a series of) patch emails + +use "hg help extensions" for information on enabling extensions +hg: unknown command 'qdel' +'qdelete' is provided by the following extension: + + mq manage a stack of patches + +use "hg help extensions" for information on enabling extensions +hg: unknown command 'churn' +'churn' is provided by the following extension: + + churn command to display statistics about repository history + +use "hg help extensions" for information on enabling extensions +% disabled extensions +churn extension - command to display statistics about repository history + +use "hg help extensions" for information on enabling extensions +patchbomb extension - command to send changesets as (a series of) patch emails + +use "hg help extensions" for information on enabling extensions +% broken disabled extension and command +broken extension - (no help text available) + +use "hg help extensions" for information on enabling extensions +hg: unknown command 'foo'