Merge branch 'rv/alias-help'
authorJunio C Hamano <gitster@pobox.com>
Fri, 26 Oct 2018 05:22:13 +0000 (14:22 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 26 Oct 2018 05:22:13 +0000 (14:22 +0900)
"git cmd --help" when "cmd" is aliased used to only say "cmd is
aliased to ...". Now it shows that to the standard error stream
and runs "git $cmd --help" where $cmd is the first word of the
alias expansion.

This could be misleading for those who alias a command with options
(e.g. with "[alias] cpn = cherry-pick -n", "git cpn --help" would
show the manual of "cherry-pick", and the reader would not be told
to pay close attention to the part that describes the "--no-commit"
option until closing the pager that showed the contents of the
manual, if the pager is configured to restore the original screen,
or would not be told at all, if the pager simply makes the message
on the standard error scroll away.

* rv/alias-help:
git-help.txt: document "git help cmd" vs "git cmd --help" for aliases
git.c: handle_alias: prepend alias info when first argument is -h
help: redirect to aliased commands for "git cmd --help"

1  2 
Documentation/git-help.txt
builtin/help.c
git.c
index 206e3aef648a7af8d033c60385c4b73b45b08683,86a6b42345d79eaa45da5b6e4c4f279eff4d39d5..aab5453bbbb239931ee2006095135c8db9f0f9ea
@@@ -8,7 -8,7 +8,7 @@@ git-help - Display help information abo
  SYNOPSIS
  --------
  [verse]
 -'git help' [-a|--all [--verbose]] [-g|--guide]
 +'git help' [-a|--all [--[no-]verbose]] [-g|--guide]
           [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE]
  
  DESCRIPTION
@@@ -29,6 -29,10 +29,10 @@@ guide is brought up. The 'man' program 
  purpose, but this can be overridden by other options or configuration
  variables.
  
+ If an alias is given, git shows the definition of the alias on
+ standard output. To get the manual page for the aliased command, use
+ `git COMMAND --help`.
  Note that `git --help ...` is identical to `git help ...` because the
  former is internally converted into the latter.
  
@@@ -42,10 -46,8 +46,10 @@@ OPTION
  --all::
        Prints all the available commands on the standard output. This
        option overrides any given command or guide name.
 -      When used with `--verbose` print description for all recognized
 -      commands.
 +
 +--verbose::
 +      When used with `--all` print description for all recognized
 +      commands. This is the default.
  
  -c::
  --config::
diff --combined builtin/help.c
index d83dac283996e59b2012ed3e17f0271f93204212,e0e3fe62e9e35f115179dad345d2bc3da5fbe5d0..7739a5c1551426fd2302aaf5daa10e454d174195
@@@ -38,7 -38,7 +38,7 @@@ static const char *html_path
  static int show_all = 0;
  static int show_guides = 0;
  static int show_config;
 -static int verbose;
 +static int verbose = 1;
  static unsigned int colopts;
  static enum help_format help_format = HELP_FORMAT_NONE;
  static int exclude_guides;
@@@ -415,9 -415,37 +415,37 @@@ static const char *check_git_cmd(const 
  
        alias = alias_lookup(cmd);
        if (alias) {
-               printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
-               free(alias);
-               exit(0);
+               const char **argv;
+               int count;
+               /*
+                * handle_builtin() in git.c rewrites "git cmd --help"
+                * to "git help --exclude-guides cmd", so we can use
+                * exclude_guides to distinguish "git cmd --help" from
+                * "git help cmd". In the latter case, or if cmd is an
+                * alias for a shell command, just print the alias
+                * definition.
+                */
+               if (!exclude_guides || alias[0] == '!') {
+                       printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
+                       free(alias);
+                       exit(0);
+               }
+               /*
+                * Otherwise, we pretend that the command was "git
+                * word0 --help". We use split_cmdline() to get the
+                * first word of the alias, to ensure that we use the
+                * same rules as when the alias is actually
+                * used. split_cmdline() modifies alias in-place.
+                */
+               fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias);
+               count = split_cmdline(alias, &argv);
+               if (count < 0)
+                       die(_("bad alias.%s string: %s"), cmd,
+                           split_cmdline_strerror(count));
+               free(argv);
+               UNLEAK(alias);
+               return alias;
        }
  
        if (exclude_guides)
diff --combined git.c
index 5920f8019bb3b26db0c7c50f9ea02f8ff96230e8,0211c2d4c05029dca2a4eccf102d659b3b796ae5..adac132956e995e2dfe050b0f709ff9aaa9c7673
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -318,6 -318,9 +318,9 @@@ static int handle_alias(int *argcp, con
        alias_command = (*argv)[0];
        alias_string = alias_lookup(alias_command);
        if (alias_string) {
+               if (*argcp > 1 && !strcmp((*argv)[1], "-h"))
+                       fprintf_ln(stderr, _("'%s' is aliased to '%s'"),
+                                  alias_command, alias_string);
                if (alias_string[0] == '!') {
                        struct child_process child = CHILD_PROCESS_INIT;
                        int nongit_ok;
@@@ -675,8 -678,6 +678,8 @@@ static void execv_dashed_external(cons
  static int run_argv(int *argcp, const char ***argv)
  {
        int done_alias = 0;
 +      struct string_list cmd_list = STRING_LIST_INIT_NODUP;
 +      struct string_list_item *seen;
  
        while (1) {
                /*
                /* .. then try the external ones */
                execv_dashed_external(*argv);
  
 -              /* It could be an alias -- this works around the insanity
 +              seen = unsorted_string_list_lookup(&cmd_list, *argv[0]);
 +              if (seen) {
 +                      int i;
 +                      struct strbuf sb = STRBUF_INIT;
 +                      for (i = 0; i < cmd_list.nr; i++) {
 +                              struct string_list_item *item = &cmd_list.items[i];
 +
 +                              strbuf_addf(&sb, "\n  %s", item->string);
 +                              if (item == seen)
 +                                      strbuf_addstr(&sb, " <==");
 +                              else if (i == cmd_list.nr - 1)
 +                                      strbuf_addstr(&sb, " ==>");
 +                      }
 +                      die(_("alias loop detected: expansion of '%s' does"
 +                            " not terminate:%s"), cmd_list.items[0].string, sb.buf);
 +              }
 +
 +              string_list_append(&cmd_list, *argv[0]);
 +
 +              /*
 +               * It could be an alias -- this works around the insanity
                 * of overriding "git log" with "git show" by having
                 * alias.log = show
                 */
 -              if (done_alias)
 -                      break;
                if (!handle_alias(argcp, argv))
                        break;
                done_alias = 1;
        }
  
 +      string_list_clear(&cmd_list, 0);
 +
        return done_alias;
  }