builtin / help.con commit help: use command-list.txt for the source of guides (1b81d8c)
   1/*
   2 * Builtin help command
   3 */
   4#include "cache.h"
   5#include "config.h"
   6#include "builtin.h"
   7#include "exec_cmd.h"
   8#include "parse-options.h"
   9#include "run-command.h"
  10#include "column.h"
  11#include "help.h"
  12
  13#ifndef DEFAULT_HELP_FORMAT
  14#define DEFAULT_HELP_FORMAT "man"
  15#endif
  16
  17static struct man_viewer_list {
  18        struct man_viewer_list *next;
  19        char name[FLEX_ARRAY];
  20} *man_viewer_list;
  21
  22static struct man_viewer_info_list {
  23        struct man_viewer_info_list *next;
  24        const char *info;
  25        char name[FLEX_ARRAY];
  26} *man_viewer_info_list;
  27
  28enum help_format {
  29        HELP_FORMAT_NONE,
  30        HELP_FORMAT_MAN,
  31        HELP_FORMAT_INFO,
  32        HELP_FORMAT_WEB
  33};
  34
  35static const char *html_path;
  36
  37static int show_all = 0;
  38static int show_guides = 0;
  39static int verbose;
  40static unsigned int colopts;
  41static enum help_format help_format = HELP_FORMAT_NONE;
  42static int exclude_guides;
  43static struct option builtin_help_options[] = {
  44        OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
  45        OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
  46        OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
  47        OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
  48        OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
  49                        HELP_FORMAT_WEB),
  50        OPT_SET_INT('i', "info", &help_format, N_("show info page"),
  51                        HELP_FORMAT_INFO),
  52        OPT__VERBOSE(&verbose, N_("print command description")),
  53        OPT_END(),
  54};
  55
  56static const char * const builtin_help_usage[] = {
  57        N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
  58        NULL
  59};
  60
  61static enum help_format parse_help_format(const char *format)
  62{
  63        if (!strcmp(format, "man"))
  64                return HELP_FORMAT_MAN;
  65        if (!strcmp(format, "info"))
  66                return HELP_FORMAT_INFO;
  67        if (!strcmp(format, "web") || !strcmp(format, "html"))
  68                return HELP_FORMAT_WEB;
  69        die(_("unrecognized help format '%s'"), format);
  70}
  71
  72static const char *get_man_viewer_info(const char *name)
  73{
  74        struct man_viewer_info_list *viewer;
  75
  76        for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
  77        {
  78                if (!strcasecmp(name, viewer->name))
  79                        return viewer->info;
  80        }
  81        return NULL;
  82}
  83
  84static int check_emacsclient_version(void)
  85{
  86        struct strbuf buffer = STRBUF_INIT;
  87        struct child_process ec_process = CHILD_PROCESS_INIT;
  88        const char *argv_ec[] = { "emacsclient", "--version", NULL };
  89        int version;
  90
  91        /* emacsclient prints its version number on stderr */
  92        ec_process.argv = argv_ec;
  93        ec_process.err = -1;
  94        ec_process.stdout_to_stderr = 1;
  95        if (start_command(&ec_process))
  96                return error(_("Failed to start emacsclient."));
  97
  98        strbuf_read(&buffer, ec_process.err, 20);
  99        close(ec_process.err);
 100
 101        /*
 102         * Don't bother checking return value, because "emacsclient --version"
 103         * seems to always exits with code 1.
 104         */
 105        finish_command(&ec_process);
 106
 107        if (!starts_with(buffer.buf, "emacsclient")) {
 108                strbuf_release(&buffer);
 109                return error(_("Failed to parse emacsclient version."));
 110        }
 111
 112        strbuf_remove(&buffer, 0, strlen("emacsclient"));
 113        version = atoi(buffer.buf);
 114
 115        if (version < 22) {
 116                strbuf_release(&buffer);
 117                return error(_("emacsclient version '%d' too old (< 22)."),
 118                        version);
 119        }
 120
 121        strbuf_release(&buffer);
 122        return 0;
 123}
 124
 125static void exec_woman_emacs(const char *path, const char *page)
 126{
 127        if (!check_emacsclient_version()) {
 128                /* This works only with emacsclient version >= 22. */
 129                struct strbuf man_page = STRBUF_INIT;
 130
 131                if (!path)
 132                        path = "emacsclient";
 133                strbuf_addf(&man_page, "(woman \"%s\")", page);
 134                execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
 135                warning_errno(_("failed to exec '%s'"), path);
 136                strbuf_release(&man_page);
 137        }
 138}
 139
 140static void exec_man_konqueror(const char *path, const char *page)
 141{
 142        const char *display = getenv("DISPLAY");
 143        if (display && *display) {
 144                struct strbuf man_page = STRBUF_INIT;
 145                const char *filename = "kfmclient";
 146
 147                /* It's simpler to launch konqueror using kfmclient. */
 148                if (path) {
 149                        size_t len;
 150                        if (strip_suffix(path, "/konqueror", &len))
 151                                path = xstrfmt("%.*s/kfmclient", (int)len, path);
 152                        filename = basename((char *)path);
 153                } else
 154                        path = "kfmclient";
 155                strbuf_addf(&man_page, "man:%s(1)", page);
 156                execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
 157                warning_errno(_("failed to exec '%s'"), path);
 158                strbuf_release(&man_page);
 159        }
 160}
 161
 162static void exec_man_man(const char *path, const char *page)
 163{
 164        if (!path)
 165                path = "man";
 166        execlp(path, "man", page, (char *)NULL);
 167        warning_errno(_("failed to exec '%s'"), path);
 168}
 169
 170static void exec_man_cmd(const char *cmd, const char *page)
 171{
 172        struct strbuf shell_cmd = STRBUF_INIT;
 173        strbuf_addf(&shell_cmd, "%s %s", cmd, page);
 174        execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL);
 175        warning(_("failed to exec '%s'"), cmd);
 176        strbuf_release(&shell_cmd);
 177}
 178
 179static void add_man_viewer(const char *name)
 180{
 181        struct man_viewer_list **p = &man_viewer_list;
 182
 183        while (*p)
 184                p = &((*p)->next);
 185        FLEX_ALLOC_STR(*p, name, name);
 186}
 187
 188static int supported_man_viewer(const char *name, size_t len)
 189{
 190        return (!strncasecmp("man", name, len) ||
 191                !strncasecmp("woman", name, len) ||
 192                !strncasecmp("konqueror", name, len));
 193}
 194
 195static void do_add_man_viewer_info(const char *name,
 196                                   size_t len,
 197                                   const char *value)
 198{
 199        struct man_viewer_info_list *new_man_viewer;
 200        FLEX_ALLOC_MEM(new_man_viewer, name, name, len);
 201        new_man_viewer->info = xstrdup(value);
 202        new_man_viewer->next = man_viewer_info_list;
 203        man_viewer_info_list = new_man_viewer;
 204}
 205
 206static int add_man_viewer_path(const char *name,
 207                               size_t len,
 208                               const char *value)
 209{
 210        if (supported_man_viewer(name, len))
 211                do_add_man_viewer_info(name, len, value);
 212        else
 213                warning(_("'%s': path for unsupported man viewer.\n"
 214                          "Please consider using 'man.<tool>.cmd' instead."),
 215                        name);
 216
 217        return 0;
 218}
 219
 220static int add_man_viewer_cmd(const char *name,
 221                              size_t len,
 222                              const char *value)
 223{
 224        if (supported_man_viewer(name, len))
 225                warning(_("'%s': cmd for supported man viewer.\n"
 226                          "Please consider using 'man.<tool>.path' instead."),
 227                        name);
 228        else
 229                do_add_man_viewer_info(name, len, value);
 230
 231        return 0;
 232}
 233
 234static int add_man_viewer_info(const char *var, const char *value)
 235{
 236        const char *name, *subkey;
 237        int namelen;
 238
 239        if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
 240                return 0;
 241
 242        if (!strcmp(subkey, "path")) {
 243                if (!value)
 244                        return config_error_nonbool(var);
 245                return add_man_viewer_path(name, namelen, value);
 246        }
 247        if (!strcmp(subkey, "cmd")) {
 248                if (!value)
 249                        return config_error_nonbool(var);
 250                return add_man_viewer_cmd(name, namelen, value);
 251        }
 252
 253        return 0;
 254}
 255
 256static int git_help_config(const char *var, const char *value, void *cb)
 257{
 258        if (starts_with(var, "column."))
 259                return git_column_config(var, value, "help", &colopts);
 260        if (!strcmp(var, "help.format")) {
 261                if (!value)
 262                        return config_error_nonbool(var);
 263                help_format = parse_help_format(value);
 264                return 0;
 265        }
 266        if (!strcmp(var, "help.htmlpath")) {
 267                if (!value)
 268                        return config_error_nonbool(var);
 269                html_path = xstrdup(value);
 270                return 0;
 271        }
 272        if (!strcmp(var, "man.viewer")) {
 273                if (!value)
 274                        return config_error_nonbool(var);
 275                add_man_viewer(value);
 276                return 0;
 277        }
 278        if (starts_with(var, "man."))
 279                return add_man_viewer_info(var, value);
 280
 281        return git_default_config(var, value, cb);
 282}
 283
 284static struct cmdnames main_cmds, other_cmds;
 285
 286static int is_git_command(const char *s)
 287{
 288        if (is_builtin(s))
 289                return 1;
 290
 291        load_command_list("git-", &main_cmds, &other_cmds);
 292        return is_in_cmdlist(&main_cmds, s) ||
 293                is_in_cmdlist(&other_cmds, s);
 294}
 295
 296static const char *cmd_to_page(const char *git_cmd)
 297{
 298        if (!git_cmd)
 299                return "git";
 300        else if (starts_with(git_cmd, "git"))
 301                return git_cmd;
 302        else if (is_git_command(git_cmd))
 303                return xstrfmt("git-%s", git_cmd);
 304        else
 305                return xstrfmt("git%s", git_cmd);
 306}
 307
 308static void setup_man_path(void)
 309{
 310        struct strbuf new_path = STRBUF_INIT;
 311        const char *old_path = getenv("MANPATH");
 312        char *git_man_path = system_path(GIT_MAN_PATH);
 313
 314        /* We should always put ':' after our path. If there is no
 315         * old_path, the ':' at the end will let 'man' to try
 316         * system-wide paths after ours to find the manual page. If
 317         * there is old_path, we need ':' as delimiter. */
 318        strbuf_addstr(&new_path, git_man_path);
 319        strbuf_addch(&new_path, ':');
 320        if (old_path)
 321                strbuf_addstr(&new_path, old_path);
 322
 323        free(git_man_path);
 324        setenv("MANPATH", new_path.buf, 1);
 325
 326        strbuf_release(&new_path);
 327}
 328
 329static void exec_viewer(const char *name, const char *page)
 330{
 331        const char *info = get_man_viewer_info(name);
 332
 333        if (!strcasecmp(name, "man"))
 334                exec_man_man(info, page);
 335        else if (!strcasecmp(name, "woman"))
 336                exec_woman_emacs(info, page);
 337        else if (!strcasecmp(name, "konqueror"))
 338                exec_man_konqueror(info, page);
 339        else if (info)
 340                exec_man_cmd(info, page);
 341        else
 342                warning(_("'%s': unknown man viewer."), name);
 343}
 344
 345static void show_man_page(const char *git_cmd)
 346{
 347        struct man_viewer_list *viewer;
 348        const char *page = cmd_to_page(git_cmd);
 349        const char *fallback = getenv("GIT_MAN_VIEWER");
 350
 351        setup_man_path();
 352        for (viewer = man_viewer_list; viewer; viewer = viewer->next)
 353        {
 354                exec_viewer(viewer->name, page); /* will return when unable */
 355        }
 356        if (fallback)
 357                exec_viewer(fallback, page);
 358        exec_viewer("man", page);
 359        die(_("no man viewer handled the request"));
 360}
 361
 362static void show_info_page(const char *git_cmd)
 363{
 364        const char *page = cmd_to_page(git_cmd);
 365        setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
 366        execlp("info", "info", "gitman", page, (char *)NULL);
 367        die(_("no info viewer handled the request"));
 368}
 369
 370static void get_html_page_path(struct strbuf *page_path, const char *page)
 371{
 372        struct stat st;
 373        char *to_free = NULL;
 374
 375        if (!html_path)
 376                html_path = to_free = system_path(GIT_HTML_PATH);
 377
 378        /* Check that we have a git documentation directory. */
 379        if (!strstr(html_path, "://")) {
 380                if (stat(mkpath("%s/git.html", html_path), &st)
 381                    || !S_ISREG(st.st_mode))
 382                        die("'%s': not a documentation directory.", html_path);
 383        }
 384
 385        strbuf_init(page_path, 0);
 386        strbuf_addf(page_path, "%s/%s.html", html_path, page);
 387        free(to_free);
 388}
 389
 390static void open_html(const char *path)
 391{
 392        execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
 393}
 394
 395static void show_html_page(const char *git_cmd)
 396{
 397        const char *page = cmd_to_page(git_cmd);
 398        struct strbuf page_path; /* it leaks but we exec bellow */
 399
 400        get_html_page_path(&page_path, page);
 401
 402        open_html(page_path.buf);
 403}
 404
 405static const char *check_git_cmd(const char* cmd)
 406{
 407        char *alias;
 408
 409        if (is_git_command(cmd))
 410                return cmd;
 411
 412        alias = alias_lookup(cmd);
 413        if (alias) {
 414                printf_ln(_("'%s' is aliased to '%s'"), cmd, alias);
 415                free(alias);
 416                exit(0);
 417        }
 418
 419        if (exclude_guides)
 420                return help_unknown_cmd(cmd);
 421
 422        return cmd;
 423}
 424
 425int cmd_help(int argc, const char **argv, const char *prefix)
 426{
 427        int nongit;
 428        enum help_format parsed_help_format;
 429
 430        argc = parse_options(argc, argv, prefix, builtin_help_options,
 431                        builtin_help_usage, 0);
 432        parsed_help_format = help_format;
 433
 434        if (show_all) {
 435                git_config(git_help_config, NULL);
 436                if (verbose) {
 437                        setup_pager();
 438                        list_all_cmds_help();
 439                        return 0;
 440                }
 441                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
 442                load_command_list("git-", &main_cmds, &other_cmds);
 443                list_commands(colopts, &main_cmds, &other_cmds);
 444        }
 445
 446        if (show_guides)
 447                list_common_guides_help();
 448
 449        if (show_all || show_guides) {
 450                printf("%s\n", _(git_more_info_string));
 451                /*
 452                * We're done. Ignore any remaining args
 453                */
 454                return 0;
 455        }
 456
 457        if (!argv[0]) {
 458                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
 459                list_common_cmds_help();
 460                printf("\n%s\n", _(git_more_info_string));
 461                return 0;
 462        }
 463
 464        setup_git_directory_gently(&nongit);
 465        git_config(git_help_config, NULL);
 466
 467        if (parsed_help_format != HELP_FORMAT_NONE)
 468                help_format = parsed_help_format;
 469        if (help_format == HELP_FORMAT_NONE)
 470                help_format = parse_help_format(DEFAULT_HELP_FORMAT);
 471
 472        argv[0] = check_git_cmd(argv[0]);
 473
 474        switch (help_format) {
 475        case HELP_FORMAT_NONE:
 476        case HELP_FORMAT_MAN:
 477                show_man_page(argv[0]);
 478                break;
 479        case HELP_FORMAT_INFO:
 480                show_info_page(argv[0]);
 481                break;
 482        case HELP_FORMAT_WEB:
 483                show_html_page(argv[0]);
 484                break;
 485        }
 486
 487        return 0;
 488}