help: supporting both help and doc for aliases
authorRodrigo Damazio <rdamazio@google.com>
Sun, 04 Mar 2018 17:11:33 -0500
changeset 37134 6890b7e991a4
parent 37133 a2a6755a3def
child 37135 ecac0006b90e
help: supporting both help and doc for aliases This allows an alias to be definted like: [alias] lj = log -Tjson lj:help = [-r REV] lj:doc = Shows the revision log in JSON format. Differential Revision: https://phab.mercurial-scm.org/D2678
hgext/show.py
mercurial/configitems.py
mercurial/dispatch.py
mercurial/help.py
mercurial/ui.py
tests/test-alias.t
tests/test-help.t
--- a/hgext/show.py	Tue Mar 27 20:21:30 2018 +0530
+++ b/hgext/show.py	Sun Mar 04 17:11:33 2018 -0500
@@ -427,7 +427,7 @@
                 continue
 
             # Same for aliases.
-            if ui.config('alias', name):
+            if ui.config('alias', name, None):
                 continue
 
             ui.setconfig('alias', name, 'show %s' % view, source='show')
--- a/mercurial/configitems.py	Tue Mar 27 20:21:30 2018 +0530
+++ b/mercurial/configitems.py	Sun Mar 04 17:11:33 2018 -0500
@@ -114,7 +114,7 @@
 coreconfigitem = getitemregister(coreitems)
 
 coreconfigitem('alias', '.*',
-    default=None,
+    default=dynamicdefault,
     generic=True,
 )
 coreconfigitem('annotate', 'nodates',
--- a/mercurial/dispatch.py	Tue Mar 27 20:21:30 2018 +0530
+++ b/mercurial/dispatch.py	Sun Mar 04 17:11:33 2018 -0500
@@ -450,7 +450,7 @@
     return r.sub(lambda x: replacemap[x.group()], cmd)
 
 class cmdalias(object):
-    def __init__(self, name, definition, cmdtable, source):
+    def __init__(self, ui, name, definition, cmdtable, source):
         self.name = self.cmd = name
         self.cmdname = ''
         self.definition = definition
@@ -477,6 +477,7 @@
             return
 
         if self.definition.startswith('!'):
+            shdef = self.definition[1:]
             self.shell = True
             def fn(ui, *args):
                 env = {'HG_ARGS': ' '.join((self.name,) + args)}
@@ -490,11 +491,12 @@
                                  "of %i variable in alias '%s' definition.\n"
                                  % (int(m.groups()[0]), self.name))
                         return ''
-                cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
+                cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
                 cmd = aliasinterpolate(self.name, args, cmd)
                 return ui.system(cmd, environ=env,
                                  blockedtag='alias_%s' % self.name)
             self.fn = fn
+            self._populatehelp(ui, name, shdef, self.fn)
             return
 
         try:
@@ -516,14 +518,12 @@
         try:
             tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
             if len(tableentry) > 2:
-                self.fn, self.opts, self.help = tableentry
+                self.fn, self.opts, cmdhelp = tableentry
             else:
                 self.fn, self.opts = tableentry
+                cmdhelp = None
 
-            if self.help.startswith("hg " + cmd):
-                # drop prefix in old-style help lines so hg shows the alias
-                self.help = self.help[4 + len(cmd):]
-            self.__doc__ = self.fn.__doc__
+            self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
 
         except error.UnknownCommand:
             self.badalias = (_("alias '%s' resolves to unknown command '%s'")
@@ -533,6 +533,14 @@
             self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
                              % (self.name, cmd))
 
+    def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
+        self.help = ui.config('alias', '%s:help' % name, defaulthelp or '')
+        if self.help and self.help.startswith("hg " + cmd):
+            # drop prefix in old-style help lines so hg shows the alias
+            self.help = self.help[4 + len(cmd):]
+
+        self.__doc__ = ui.config('alias', '%s:doc' % name, fn.__doc__)
+
     @property
     def args(self):
         args = pycompat.maplist(util.expandpath, self.givenargs)
@@ -577,7 +585,8 @@
 class lazyaliasentry(object):
     """like a typical command entry (func, opts, help), but is lazy"""
 
-    def __init__(self, name, definition, cmdtable, source):
+    def __init__(self, ui, name, definition, cmdtable, source):
+        self.ui = ui
         self.name = name
         self.definition = definition
         self.cmdtable = cmdtable.copy()
@@ -585,7 +594,8 @@
 
     @util.propertycache
     def _aliasdef(self):
-        return cmdalias(self.name, self.definition, self.cmdtable, self.source)
+        return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
+                        self.source)
 
     def __getitem__(self, n):
         aliasdef = self._aliasdef
@@ -609,7 +619,7 @@
     # aliases are processed after extensions have been loaded, so they
     # may use extension commands. Aliases can also use other alias definitions,
     # but only if they have been defined prior to the current definition.
-    for alias, definition in ui.configitems('alias'):
+    for alias, definition in ui.configitems('alias', ignoresub=True):
         try:
             if cmdtable[alias].definition == definition:
                 continue
@@ -618,7 +628,7 @@
             pass
 
         source = ui.configsource('alias', alias)
-        entry = lazyaliasentry(alias, definition, cmdtable, source)
+        entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
         cmdtable[alias] = entry
 
 def _parse(ui, args):
--- a/mercurial/help.py	Tue Mar 27 20:21:30 2018 +0530
+++ b/mercurial/help.py	Sun Mar 04 17:11:33 2018 -0500
@@ -370,8 +370,8 @@
         if util.safehasattr(entry[0], 'definition'):  # aliased command
             source = entry[0].source
             if entry[0].definition.startswith('!'):  # shell alias
-                doc = (_('shell alias for::\n\n    %s\n\ndefined by: %s\n') %
-                       (entry[0].definition[1:], source))
+                doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
+                       (entry[0].definition[1:], doc, source))
             else:
                 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
                        (entry[0].definition, doc, source))
--- a/mercurial/ui.py	Tue Mar 27 20:21:30 2018 +0530
+++ b/mercurial/ui.py	Sun Mar 04 17:11:33 2018 -0500
@@ -739,11 +739,7 @@
     def configitems(self, section, untrusted=False, ignoresub=False):
         items = self._data(untrusted).items(section)
         if ignoresub:
-            newitems = {}
-            for k, v in items:
-                if ':' not in k:
-                    newitems[k] = v
-            items = list(newitems.iteritems())
+            items = [i for i in items if ':' not in i[0]]
         if self.debugflag and not untrusted and self._reportuntrusted:
             for k, v in self._ucfg.items(section):
                 if self._tcfg.get(section, k) != v:
--- a/tests/test-alias.t	Tue Mar 27 20:21:30 2018 +0530
+++ b/tests/test-alias.t	Sun Mar 04 17:11:33 2018 -0500
@@ -4,9 +4,13 @@
   > # should clobber ci but not commit (issue2993)
   > ci = version
   > myinit = init
+  > myinit:doc = This is my documented alias for init.
+  > myinit:help = [OPTIONS] [BLA] [BLE]
   > mycommit = commit
+  > mycommit:doc = This is my alias with only doc.
   > optionalrepo = showconfig alias.myinit
   > cleanstatus = status -c
+  > cleanstatus:help = [ONLYHELPHERE]
   > unknown = bargle
   > ambiguous = s
   > recursive = recursive
@@ -20,9 +24,13 @@
   > no--config = status --config a.config=1
   > mylog = log
   > lognull = log -r null
+  > lognull:doc = Logs the null rev
+  > lognull:help = foo bar baz
   > shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
   > positional = log --template '{\$2} {\$1} | {date|isodate}\n'
   > dln = lognull --debug
+  > recursivedoc = dln
+  > recursivedoc:doc = Logs the null rev in debug mode
   > nousage = rollback
   > put = export -r 0 -o "\$FOO/%R.diff"
   > blank = !printf '\n'
@@ -53,11 +61,148 @@
   > log = -v
   > EOF
 
-
 basic
 
   $ hg myinit alias
 
+help
+
+  $ hg help -c | grep myinit
+   myinit             This is my documented alias for init.
+  $ hg help -c | grep mycommit
+   mycommit           This is my alias with only doc.
+  $ hg help -c | grep cleanstatus
+   cleanstatus        show changed files in the working directory
+  $ hg help -c | grep lognull
+   lognull            Logs the null rev
+  $ hg help -c | grep dln
+   dln                Logs the null rev
+  $ hg help -c | grep recursivedoc
+   recursivedoc       Logs the null rev in debug mode
+  $ hg help myinit
+  hg myinit [OPTIONS] [BLA] [BLE]
+  
+  alias for: hg init
+  
+  This is my documented alias for init.
+  
+  defined by: * (glob)
+  */* (glob) (?)
+  */* (glob) (?)
+  */* (glob) (?)
+  
+  options:
+  
+   -e --ssh CMD       specify ssh command to use
+      --remotecmd CMD specify hg command to run on the remote side
+      --insecure      do not verify server certificate (ignoring web.cacerts
+                      config)
+  
+  (some details hidden, use --verbose to show complete help)
+
+  $ hg help mycommit
+  hg mycommit [OPTION]... [FILE]...
+  
+  alias for: hg commit
+  
+  This is my alias with only doc.
+  
+  defined by: * (glob)
+  */* (glob) (?)
+  */* (glob) (?)
+  */* (glob) (?)
+  
+  options ([+] can be repeated):
+  
+   -A --addremove           mark new/missing files as added/removed before
+                            committing
+      --close-branch        mark a branch head as closed
+      --amend               amend the parent of the working directory
+   -s --secret              use the secret phase for committing
+   -e --edit                invoke editor on commit messages
+   -i --interactive         use interactive mode
+   -I --include PATTERN [+] include names matching the given patterns
+   -X --exclude PATTERN [+] exclude names matching the given patterns
+   -m --message TEXT        use text as commit message
+   -l --logfile FILE        read commit message from file
+   -d --date DATE           record the specified date as commit date
+   -u --user USER           record the specified user as committer
+   -S --subrepos            recurse into subrepositories
+  
+  (some details hidden, use --verbose to show complete help)
+
+  $ hg help cleanstatus
+  hg cleanstatus [ONLYHELPHERE]
+  
+  alias for: hg status -c
+  
+  show changed files in the working directory
+  
+      Show status of files in the repository. If names are given, only files
+      that match are shown. Files that are clean or ignored or the source of a
+      copy/move operation, are not listed unless -c/--clean, -i/--ignored,
+      -C/--copies or -A/--all are given. Unless options described with "show
+      only ..." are given, the options -mardu are used.
+  
+      Option -q/--quiet hides untracked (unknown and ignored) files unless
+      explicitly requested with -u/--unknown or -i/--ignored.
+  
+      Note:
+         'hg status' may appear to disagree with diff if permissions have
+         changed or a merge has occurred. The standard diff format does not
+         report permission changes and diff only reports changes relative to one
+         merge parent.
+  
+      If one revision is given, it is used as the base revision. If two
+      revisions are given, the differences between them are shown. The --change
+      option can also be used as a shortcut to list the changed files of a
+      revision from its first parent.
+  
+      The codes used to show the status of files are:
+  
+        M = modified
+        A = added
+        R = removed
+        C = clean
+        ! = missing (deleted by non-hg command, but still tracked)
+        ? = not tracked
+        I = ignored
+          = origin of the previous file (with --copies)
+  
+      Returns 0 on success.
+  
+  defined by: * (glob)
+  */* (glob) (?)
+  */* (glob) (?)
+  */* (glob) (?)
+  
+  options ([+] can be repeated):
+  
+   -A --all                 show status of all files
+   -m --modified            show only modified files
+   -a --added               show only added files
+   -r --removed             show only removed files
+   -d --deleted             show only deleted (but tracked) files
+   -c --clean               show only files without changes
+   -u --unknown             show only unknown (not tracked) files
+   -i --ignored             show only ignored files
+   -n --no-status           hide status prefix
+   -C --copies              show source of copied files
+   -0 --print0              end filenames with NUL, for use with xargs
+      --rev REV [+]         show difference from revision
+      --change REV          list the changed files of a revision
+   -I --include PATTERN [+] include names matching the given patterns
+   -X --exclude PATTERN [+] exclude names matching the given patterns
+   -S --subrepos            recurse into subrepositories
+  
+  (some details hidden, use --verbose to show complete help)
+
+  $ hg help recursivedoc | head -n 5
+  hg recursivedoc foo bar baz
+  
+  alias for: hg dln
+  
+  Logs the null rev in debug mode
 
 unknown
 
@@ -440,6 +585,10 @@
   > rebate = !echo this is \$HG_ARGS
   > EOF
 #endif
+  $ cat >> .hg/hgrc <<EOF
+  > rebate:doc = This is my alias which just prints something.
+  > rebate:help = [MYARGS]
+  > EOF
   $ hg reba
   hg: command 'reba' is ambiguous:
       rebase rebate
@@ -449,6 +598,24 @@
   $ hg rebat --foo-bar
   this is rebate --foo-bar
 
+help for a shell alias
+
+  $ hg help -c | grep rebate
+   rebate             This is my alias which just prints something.
+  $ hg help rebate
+  hg rebate [MYARGS]
+  
+  shell alias for: echo this is $HG_ARGS
+  
+  This is my alias which just prints something.
+  
+  defined by:* (glob)
+  */* (glob) (?)
+  */* (glob) (?)
+  */* (glob) (?)
+  
+  (some details hidden, use --verbose to show complete help)
+
 invalid arguments
 
   $ hg rt foo
--- a/tests/test-help.t	Tue Mar 27 20:21:30 2018 +0530
+++ b/tests/test-help.t	Sun Mar 04 17:11:33 2018 -0500
@@ -777,9 +777,9 @@
   $ hg help shellalias
   hg shellalias
   
-  shell alias for:
-  
-    echo hi
+  shell alias for: echo hi
+  
+  (no help text available)
   
   defined by: helpext