builtin / help.con commit Allow help.htmlpath to be a URL prefix (86272b4)
   1/*
   2 * builtin-help.c
   3 *
   4 * Builtin help command
   5 */
   6#include "cache.h"
   7#include "builtin.h"
   8#include "exec_cmd.h"
   9#include "common-cmds.h"
  10#include "parse-options.h"
  11#include "run-command.h"
  12#include "column.h"
  13#include "help.h"
  14
  15static struct man_viewer_list {
  16        struct man_viewer_list *next;
  17        char name[FLEX_ARRAY];
  18} *man_viewer_list;
  19
  20static struct man_viewer_info_list {
  21        struct man_viewer_info_list *next;
  22        const char *info;
  23        char name[FLEX_ARRAY];
  24} *man_viewer_info_list;
  25
  26enum help_format {
  27        HELP_FORMAT_NONE,
  28        HELP_FORMAT_MAN,
  29        HELP_FORMAT_INFO,
  30        HELP_FORMAT_WEB
  31};
  32
  33static const char *html_path;
  34
  35static int show_all = 0;
  36static unsigned int colopts;
  37static enum help_format help_format = HELP_FORMAT_NONE;
  38static struct option builtin_help_options[] = {
  39        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
  40        OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
  41        OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
  42                        HELP_FORMAT_WEB),
  43        OPT_SET_INT('i', "info", &help_format, "show info page",
  44                        HELP_FORMAT_INFO),
  45        OPT_END(),
  46};
  47
  48static const char * const builtin_help_usage[] = {
  49        "git help [--all] [--man|--web|--info] [command]",
  50        NULL
  51};
  52
  53static enum help_format parse_help_format(const char *format)
  54{
  55        if (!strcmp(format, "man"))
  56                return HELP_FORMAT_MAN;
  57        if (!strcmp(format, "info"))
  58                return HELP_FORMAT_INFO;
  59        if (!strcmp(format, "web") || !strcmp(format, "html"))
  60                return HELP_FORMAT_WEB;
  61        die(_("unrecognized help format '%s'"), format);
  62}
  63
  64static const char *get_man_viewer_info(const char *name)
  65{
  66        struct man_viewer_info_list *viewer;
  67
  68        for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
  69        {
  70                if (!strcasecmp(name, viewer->name))
  71                        return viewer->info;
  72        }
  73        return NULL;
  74}
  75
  76static int check_emacsclient_version(void)
  77{
  78        struct strbuf buffer = STRBUF_INIT;
  79        struct child_process ec_process;
  80        const char *argv_ec[] = { "emacsclient", "--version", NULL };
  81        int version;
  82
  83        /* emacsclient prints its version number on stderr */
  84        memset(&ec_process, 0, sizeof(ec_process));
  85        ec_process.argv = argv_ec;
  86        ec_process.err = -1;
  87        ec_process.stdout_to_stderr = 1;
  88        if (start_command(&ec_process))
  89                return error(_("Failed to start emacsclient."));
  90
  91        strbuf_read(&buffer, ec_process.err, 20);
  92        close(ec_process.err);
  93
  94        /*
  95         * Don't bother checking return value, because "emacsclient --version"
  96         * seems to always exits with code 1.
  97         */
  98        finish_command(&ec_process);
  99
 100        if (prefixcmp(buffer.buf, "emacsclient")) {
 101                strbuf_release(&buffer);
 102                return error(_("Failed to parse emacsclient version."));
 103        }
 104
 105        strbuf_remove(&buffer, 0, strlen("emacsclient"));
 106        version = atoi(buffer.buf);
 107
 108        if (version < 22) {
 109                strbuf_release(&buffer);
 110                return error(_("emacsclient version '%d' too old (< 22)."),
 111                        version);
 112        }
 113
 114        strbuf_release(&buffer);
 115        return 0;
 116}
 117
 118static void exec_woman_emacs(const char *path, const char *page)
 119{
 120        if (!check_emacsclient_version()) {
 121                /* This works only with emacsclient version >= 22. */
 122                struct strbuf man_page = STRBUF_INIT;
 123
 124                if (!path)
 125                        path = "emacsclient";
 126                strbuf_addf(&man_page, "(woman \"%s\")", page);
 127                execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
 128                warning(_("failed to exec '%s': %s"), path, strerror(errno));
 129        }
 130}
 131
 132static void exec_man_konqueror(const char *path, const char *page)
 133{
 134        const char *display = getenv("DISPLAY");
 135        if (display && *display) {
 136                struct strbuf man_page = STRBUF_INIT;
 137                const char *filename = "kfmclient";
 138
 139                /* It's simpler to launch konqueror using kfmclient. */
 140                if (path) {
 141                        const char *file = strrchr(path, '/');
 142                        if (file && !strcmp(file + 1, "konqueror")) {
 143                                char *new = xstrdup(path);
 144                                char *dest = strrchr(new, '/');
 145
 146                                /* strlen("konqueror") == strlen("kfmclient") */
 147                                strcpy(dest + 1, "kfmclient");
 148                                path = new;
 149                        }
 150                        if (file)
 151                                filename = file;
 152                } else
 153                        path = "kfmclient";
 154                strbuf_addf(&man_page, "man:%s(1)", page);
 155                execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
 156                warning(_("failed to exec '%s': %s"), path, strerror(errno));
 157        }
 158}
 159
 160static void exec_man_man(const char *path, const char *page)
 161{
 162        if (!path)
 163                path = "man";
 164        execlp(path, "man", page, (char *)NULL);
 165        warning(_("failed to exec '%s': %s"), path, strerror(errno));
 166}
 167
 168static void exec_man_cmd(const char *cmd, const char *page)
 169{
 170        struct strbuf shell_cmd = STRBUF_INIT;
 171        strbuf_addf(&shell_cmd, "%s %s", cmd, page);
 172        execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
 173        warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
 174}
 175
 176static void add_man_viewer(const char *name)
 177{
 178        struct man_viewer_list **p = &man_viewer_list;
 179        size_t len = strlen(name);
 180
 181        while (*p)
 182                p = &((*p)->next);
 183        *p = xcalloc(1, (sizeof(**p) + len + 1));
 184        strncpy((*p)->name, name, len);
 185}
 186
 187static int supported_man_viewer(const char *name, size_t len)
 188{
 189        return (!strncasecmp("man", name, len) ||
 190                !strncasecmp("woman", name, len) ||
 191                !strncasecmp("konqueror", name, len));
 192}
 193
 194static void do_add_man_viewer_info(const char *name,
 195                                   size_t len,
 196                                   const char *value)
 197{
 198        struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
 199
 200        strncpy(new->name, name, len);
 201        new->info = xstrdup(value);
 202        new->next = man_viewer_info_list;
 203        man_viewer_info_list = new;
 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 = var + 4;
 237        const char *subkey = strrchr(name, '.');
 238
 239        if (!subkey)
 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, subkey - name, value);
 246        }
 247        if (!strcmp(subkey, ".cmd")) {
 248                if (!value)
 249                        return config_error_nonbool(var);
 250                return add_man_viewer_cmd(name, subkey - name, value);
 251        }
 252
 253        return 0;
 254}
 255
 256static int git_help_config(const char *var, const char *value, void *cb)
 257{
 258        if (!prefixcmp(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 (!prefixcmp(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
 286void list_common_cmds_help(void)
 287{
 288        int i, longest = 0;
 289
 290        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 291                if (longest < strlen(common_cmds[i].name))
 292                        longest = strlen(common_cmds[i].name);
 293        }
 294
 295        puts(_("The most commonly used git commands are:"));
 296        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 297                printf("   %s   ", common_cmds[i].name);
 298                mput_char(' ', longest - strlen(common_cmds[i].name));
 299                puts(_(common_cmds[i].help));
 300        }
 301}
 302
 303static int is_git_command(const char *s)
 304{
 305        return is_in_cmdlist(&main_cmds, s) ||
 306                is_in_cmdlist(&other_cmds, s);
 307}
 308
 309static const char *prepend(const char *prefix, const char *cmd)
 310{
 311        size_t pre_len = strlen(prefix);
 312        size_t cmd_len = strlen(cmd);
 313        char *p = xmalloc(pre_len + cmd_len + 1);
 314        memcpy(p, prefix, pre_len);
 315        strcpy(p + pre_len, cmd);
 316        return p;
 317}
 318
 319static const char *cmd_to_page(const char *git_cmd)
 320{
 321        if (!git_cmd)
 322                return "git";
 323        else if (!prefixcmp(git_cmd, "git"))
 324                return git_cmd;
 325        else if (is_git_command(git_cmd))
 326                return prepend("git-", git_cmd);
 327        else
 328                return prepend("git", git_cmd);
 329}
 330
 331static void setup_man_path(void)
 332{
 333        struct strbuf new_path = STRBUF_INIT;
 334        const char *old_path = getenv("MANPATH");
 335
 336        /* We should always put ':' after our path. If there is no
 337         * old_path, the ':' at the end will let 'man' to try
 338         * system-wide paths after ours to find the manual page. If
 339         * there is old_path, we need ':' as delimiter. */
 340        strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
 341        strbuf_addch(&new_path, ':');
 342        if (old_path)
 343                strbuf_addstr(&new_path, old_path);
 344
 345        setenv("MANPATH", new_path.buf, 1);
 346
 347        strbuf_release(&new_path);
 348}
 349
 350static void exec_viewer(const char *name, const char *page)
 351{
 352        const char *info = get_man_viewer_info(name);
 353
 354        if (!strcasecmp(name, "man"))
 355                exec_man_man(info, page);
 356        else if (!strcasecmp(name, "woman"))
 357                exec_woman_emacs(info, page);
 358        else if (!strcasecmp(name, "konqueror"))
 359                exec_man_konqueror(info, page);
 360        else if (info)
 361                exec_man_cmd(info, page);
 362        else
 363                warning(_("'%s': unknown man viewer."), name);
 364}
 365
 366static void show_man_page(const char *git_cmd)
 367{
 368        struct man_viewer_list *viewer;
 369        const char *page = cmd_to_page(git_cmd);
 370        const char *fallback = getenv("GIT_MAN_VIEWER");
 371
 372        setup_man_path();
 373        for (viewer = man_viewer_list; viewer; viewer = viewer->next)
 374        {
 375                exec_viewer(viewer->name, page); /* will return when unable */
 376        }
 377        if (fallback)
 378                exec_viewer(fallback, page);
 379        exec_viewer("man", page);
 380        die(_("no man viewer handled the request"));
 381}
 382
 383static void show_info_page(const char *git_cmd)
 384{
 385        const char *page = cmd_to_page(git_cmd);
 386        setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
 387        execlp("info", "info", "gitman", page, (char *)NULL);
 388        die(_("no info viewer handled the request"));
 389}
 390
 391static void get_html_page_path(struct strbuf *page_path, const char *page)
 392{
 393        struct stat st;
 394        if (!html_path)
 395                html_path = system_path(GIT_HTML_PATH);
 396
 397        /* Check that we have a git documentation directory. */
 398        if (!strstr(html_path, "://")) {
 399                if (stat(mkpath("%s/git.html", html_path), &st)
 400                    || !S_ISREG(st.st_mode))
 401                        die("'%s': not a documentation directory.", html_path);
 402        }
 403
 404        strbuf_init(page_path, 0);
 405        strbuf_addf(page_path, "%s/%s.html", html_path, page);
 406}
 407
 408/*
 409 * If open_html is not defined in a platform-specific way (see for
 410 * example compat/mingw.h), we use the script web--browse to display
 411 * HTML.
 412 */
 413#ifndef open_html
 414static void open_html(const char *path)
 415{
 416        execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
 417}
 418#endif
 419
 420static void show_html_page(const char *git_cmd)
 421{
 422        const char *page = cmd_to_page(git_cmd);
 423        struct strbuf page_path; /* it leaks but we exec bellow */
 424
 425        get_html_page_path(&page_path, page);
 426
 427        open_html(page_path.buf);
 428}
 429
 430int cmd_help(int argc, const char **argv, const char *prefix)
 431{
 432        int nongit;
 433        const char *alias;
 434        enum help_format parsed_help_format;
 435        load_command_list("git-", &main_cmds, &other_cmds);
 436
 437        argc = parse_options(argc, argv, prefix, builtin_help_options,
 438                        builtin_help_usage, 0);
 439        parsed_help_format = help_format;
 440
 441        if (show_all) {
 442                git_config(git_help_config, NULL);
 443                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
 444                list_commands(colopts, &main_cmds, &other_cmds);
 445                printf("%s\n", _(git_more_info_string));
 446                return 0;
 447        }
 448
 449        if (!argv[0]) {
 450                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
 451                list_common_cmds_help();
 452                printf("\n%s\n", _(git_more_info_string));
 453                return 0;
 454        }
 455
 456        setup_git_directory_gently(&nongit);
 457        git_config(git_help_config, NULL);
 458
 459        if (parsed_help_format != HELP_FORMAT_NONE)
 460                help_format = parsed_help_format;
 461
 462        alias = alias_lookup(argv[0]);
 463        if (alias && !is_git_command(argv[0])) {
 464                printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
 465                return 0;
 466        }
 467
 468        switch (help_format) {
 469        case HELP_FORMAT_NONE:
 470        case HELP_FORMAT_MAN:
 471                show_man_page(argv[0]);
 472                break;
 473        case HELP_FORMAT_INFO:
 474                show_info_page(argv[0]);
 475                break;
 476        case HELP_FORMAT_WEB:
 477                show_html_page(argv[0]);
 478                break;
 479        }
 480
 481        return 0;
 482}