483e1e9c3824a39d231fca353c2fd68f1a68f840
   1/*
   2 * builtin-help.c
   3 *
   4 * Builtin help-related commands (help, usage, version)
   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
  13static struct man_viewer_list {
  14        struct man_viewer_list *next;
  15        char name[FLEX_ARRAY];
  16} *man_viewer_list;
  17
  18static struct man_viewer_info_list {
  19        struct man_viewer_info_list *next;
  20        const char *info;
  21        char name[FLEX_ARRAY];
  22} *man_viewer_info_list;
  23
  24enum help_format {
  25        HELP_FORMAT_MAN,
  26        HELP_FORMAT_INFO,
  27        HELP_FORMAT_WEB,
  28};
  29
  30static int show_all = 0;
  31static enum help_format help_format = HELP_FORMAT_MAN;
  32static struct option builtin_help_options[] = {
  33        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
  34        OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
  35        OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
  36                        HELP_FORMAT_WEB),
  37        OPT_SET_INT('i', "info", &help_format, "show info page",
  38                        HELP_FORMAT_INFO),
  39        OPT_END(),
  40};
  41
  42static const char * const builtin_help_usage[] = {
  43        "git-help [--all] [--man|--web|--info] [command]",
  44        NULL
  45};
  46
  47static enum help_format parse_help_format(const char *format)
  48{
  49        if (!strcmp(format, "man"))
  50                return HELP_FORMAT_MAN;
  51        if (!strcmp(format, "info"))
  52                return HELP_FORMAT_INFO;
  53        if (!strcmp(format, "web") || !strcmp(format, "html"))
  54                return HELP_FORMAT_WEB;
  55        die("unrecognized help format '%s'", format);
  56}
  57
  58static const char *get_man_viewer_info(const char *name)
  59{
  60        struct man_viewer_info_list *viewer;
  61
  62        for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
  63        {
  64                if (!strcasecmp(name, viewer->name))
  65                        return viewer->info;
  66        }
  67        return NULL;
  68}
  69
  70static int check_emacsclient_version(void)
  71{
  72        struct strbuf buffer = STRBUF_INIT;
  73        struct child_process ec_process;
  74        const char *argv_ec[] = { "emacsclient", "--version", NULL };
  75        int version;
  76
  77        /* emacsclient prints its version number on stderr */
  78        memset(&ec_process, 0, sizeof(ec_process));
  79        ec_process.argv = argv_ec;
  80        ec_process.err = -1;
  81        ec_process.stdout_to_stderr = 1;
  82        if (start_command(&ec_process)) {
  83                fprintf(stderr, "Failed to start emacsclient.\n");
  84                return -1;
  85        }
  86        strbuf_read(&buffer, ec_process.err, 20);
  87        close(ec_process.err);
  88
  89        /*
  90         * Don't bother checking return value, because "emacsclient --version"
  91         * seems to always exits with code 1.
  92         */
  93        finish_command(&ec_process);
  94
  95        if (prefixcmp(buffer.buf, "emacsclient")) {
  96                fprintf(stderr, "Failed to parse emacsclient version.\n");
  97                strbuf_release(&buffer);
  98                return -1;
  99        }
 100
 101        strbuf_remove(&buffer, 0, strlen("emacsclient"));
 102        version = atoi(buffer.buf);
 103
 104        if (version < 22) {
 105                fprintf(stderr,
 106                        "emacsclient version '%d' too old (< 22).\n",
 107                        version);
 108                strbuf_release(&buffer);
 109                return -1;
 110        }
 111
 112        strbuf_release(&buffer);
 113        return 0;
 114}
 115
 116static void exec_woman_emacs(const char* path, const char *page)
 117{
 118        if (!check_emacsclient_version()) {
 119                /* This works only with emacsclient version >= 22. */
 120                struct strbuf man_page = STRBUF_INIT;
 121
 122                if (!path)
 123                        path = "emacsclient";
 124                strbuf_addf(&man_page, "(woman \"%s\")", page);
 125                execlp(path, "emacsclient", "-e", man_page.buf, NULL);
 126                warning("failed to exec '%s': %s", path, strerror(errno));
 127        }
 128}
 129
 130static void exec_man_konqueror(const char* path, const char *page)
 131{
 132        const char *display = getenv("DISPLAY");
 133        if (display && *display) {
 134                struct strbuf man_page = STRBUF_INIT;
 135                const char *filename = "kfmclient";
 136
 137                /* It's simpler to launch konqueror using kfmclient. */
 138                if (path) {
 139                        const char *file = strrchr(path, '/');
 140                        if (file && !strcmp(file + 1, "konqueror")) {
 141                                char *new = xstrdup(path);
 142                                char *dest = strrchr(new, '/');
 143
 144                                /* strlen("konqueror") == strlen("kfmclient") */
 145                                strcpy(dest + 1, "kfmclient");
 146                                path = new;
 147                        }
 148                        if (file)
 149                                filename = file;
 150                } else
 151                        path = "kfmclient";
 152                strbuf_addf(&man_page, "man:%s(1)", page);
 153                execlp(path, filename, "newTab", man_page.buf, NULL);
 154                warning("failed to exec '%s': %s", path, strerror(errno));
 155        }
 156}
 157
 158static void exec_man_man(const char* path, const char *page)
 159{
 160        if (!path)
 161                path = "man";
 162        execlp(path, "man", page, NULL);
 163        warning("failed to exec '%s': %s", path, strerror(errno));
 164}
 165
 166static void do_add_man_viewer(const char *name)
 167{
 168        struct man_viewer_list **p = &man_viewer_list;
 169        size_t len = strlen(name);
 170
 171        while (*p)
 172                p = &((*p)->next);
 173        *p = xcalloc(1, (sizeof(**p) + len + 1));
 174        strncpy((*p)->name, name, len);
 175}
 176
 177static int supported_man_viewer(const char *name, size_t len)
 178{
 179        return (!strncasecmp("man", name, len) ||
 180                !strncasecmp("woman", name, len) ||
 181                !strncasecmp("konqueror", name, len));
 182}
 183
 184static int add_man_viewer(const char *value)
 185{
 186        if (supported_man_viewer(value, strlen(value)))
 187                do_add_man_viewer(value);
 188        else
 189                warning("'%s': unsupported man viewer.", value);
 190
 191        return 0;
 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.", name);
 214
 215        return 0;
 216}
 217
 218static int add_man_viewer_info(const char *var, const char *value)
 219{
 220        const char *name = var + 4;
 221        const char *subkey = strrchr(name, '.');
 222
 223        if (!subkey)
 224                return error("Config with no key for man viewer: %s", name);
 225
 226        if (!strcmp(subkey, ".path")) {
 227                if (!value)
 228                        return config_error_nonbool(var);
 229                return add_man_viewer_path(name, subkey - name, value);
 230        }
 231
 232        warning("'%s': unsupported man viewer sub key.", subkey);
 233        return 0;
 234}
 235
 236static int git_help_config(const char *var, const char *value)
 237{
 238        if (!strcmp(var, "help.format")) {
 239                if (!value)
 240                        return config_error_nonbool(var);
 241                help_format = parse_help_format(value);
 242                return 0;
 243        }
 244        if (!strcmp(var, "man.viewer")) {
 245                if (!value)
 246                        return config_error_nonbool(var);
 247                return add_man_viewer(value);
 248        }
 249        if (!prefixcmp(var, "man."))
 250                return add_man_viewer_info(var, value);
 251
 252        return git_default_config(var, value);
 253}
 254
 255/* most GUI terminals set COLUMNS (although some don't export it) */
 256static int term_columns(void)
 257{
 258        char *col_string = getenv("COLUMNS");
 259        int n_cols;
 260
 261        if (col_string && (n_cols = atoi(col_string)) > 0)
 262                return n_cols;
 263
 264#ifdef TIOCGWINSZ
 265        {
 266                struct winsize ws;
 267                if (!ioctl(1, TIOCGWINSZ, &ws)) {
 268                        if (ws.ws_col)
 269                                return ws.ws_col;
 270                }
 271        }
 272#endif
 273
 274        return 80;
 275}
 276
 277static inline void mput_char(char c, unsigned int num)
 278{
 279        while(num--)
 280                putchar(c);
 281}
 282
 283static struct cmdnames {
 284        int alloc;
 285        int cnt;
 286        struct cmdname {
 287                size_t len;
 288                char name[1];
 289        } **names;
 290} main_cmds, other_cmds;
 291
 292static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 293{
 294        struct cmdname *ent = xmalloc(sizeof(*ent) + len);
 295
 296        ent->len = len;
 297        memcpy(ent->name, name, len);
 298        ent->name[len] = 0;
 299
 300        ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
 301        cmds->names[cmds->cnt++] = ent;
 302}
 303
 304static int cmdname_compare(const void *a_, const void *b_)
 305{
 306        struct cmdname *a = *(struct cmdname **)a_;
 307        struct cmdname *b = *(struct cmdname **)b_;
 308        return strcmp(a->name, b->name);
 309}
 310
 311static void uniq(struct cmdnames *cmds)
 312{
 313        int i, j;
 314
 315        if (!cmds->cnt)
 316                return;
 317
 318        for (i = j = 1; i < cmds->cnt; i++)
 319                if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
 320                        cmds->names[j++] = cmds->names[i];
 321
 322        cmds->cnt = j;
 323}
 324
 325static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
 326{
 327        int ci, cj, ei;
 328        int cmp;
 329
 330        ci = cj = ei = 0;
 331        while (ci < cmds->cnt && ei < excludes->cnt) {
 332                cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
 333                if (cmp < 0)
 334                        cmds->names[cj++] = cmds->names[ci++];
 335                else if (cmp == 0)
 336                        ci++, ei++;
 337                else if (cmp > 0)
 338                        ei++;
 339        }
 340
 341        while (ci < cmds->cnt)
 342                cmds->names[cj++] = cmds->names[ci++];
 343
 344        cmds->cnt = cj;
 345}
 346
 347static void pretty_print_string_list(struct cmdnames *cmds, int longest)
 348{
 349        int cols = 1, rows;
 350        int space = longest + 1; /* min 1 SP between words */
 351        int max_cols = term_columns() - 1; /* don't print *on* the edge */
 352        int i, j;
 353
 354        if (space < max_cols)
 355                cols = max_cols / space;
 356        rows = (cmds->cnt + cols - 1) / cols;
 357
 358        for (i = 0; i < rows; i++) {
 359                printf("  ");
 360
 361                for (j = 0; j < cols; j++) {
 362                        int n = j * rows + i;
 363                        int size = space;
 364                        if (n >= cmds->cnt)
 365                                break;
 366                        if (j == cols-1 || n + rows >= cmds->cnt)
 367                                size = 1;
 368                        printf("%-*s", size, cmds->names[n]->name);
 369                }
 370                putchar('\n');
 371        }
 372}
 373
 374static unsigned int list_commands_in_dir(struct cmdnames *cmds,
 375                                         const char *path)
 376{
 377        unsigned int longest = 0;
 378        const char *prefix = "git-";
 379        int prefix_len = strlen(prefix);
 380        DIR *dir = opendir(path);
 381        struct dirent *de;
 382
 383        if (!dir || chdir(path))
 384                return 0;
 385
 386        while ((de = readdir(dir)) != NULL) {
 387                struct stat st;
 388                int entlen;
 389
 390                if (prefixcmp(de->d_name, prefix))
 391                        continue;
 392
 393                if (stat(de->d_name, &st) || /* stat, not lstat */
 394                    !S_ISREG(st.st_mode) ||
 395                    !(st.st_mode & S_IXUSR))
 396                        continue;
 397
 398                entlen = strlen(de->d_name) - prefix_len;
 399                if (has_extension(de->d_name, ".exe"))
 400                        entlen -= 4;
 401
 402                if (longest < entlen)
 403                        longest = entlen;
 404
 405                add_cmdname(cmds, de->d_name + prefix_len, entlen);
 406        }
 407        closedir(dir);
 408
 409        return longest;
 410}
 411
 412static unsigned int load_command_list(void)
 413{
 414        unsigned int longest = 0;
 415        unsigned int len;
 416        const char *env_path = getenv("PATH");
 417        char *paths, *path, *colon;
 418        const char *exec_path = git_exec_path();
 419
 420        if (exec_path)
 421                longest = list_commands_in_dir(&main_cmds, exec_path);
 422
 423        if (!env_path) {
 424                fprintf(stderr, "PATH not set\n");
 425                exit(1);
 426        }
 427
 428        path = paths = xstrdup(env_path);
 429        while (1) {
 430                if ((colon = strchr(path, ':')))
 431                        *colon = 0;
 432
 433                len = list_commands_in_dir(&other_cmds, path);
 434                if (len > longest)
 435                        longest = len;
 436
 437                if (!colon)
 438                        break;
 439                path = colon + 1;
 440        }
 441        free(paths);
 442
 443        qsort(main_cmds.names, main_cmds.cnt,
 444              sizeof(*main_cmds.names), cmdname_compare);
 445        uniq(&main_cmds);
 446
 447        qsort(other_cmds.names, other_cmds.cnt,
 448              sizeof(*other_cmds.names), cmdname_compare);
 449        uniq(&other_cmds);
 450        exclude_cmds(&other_cmds, &main_cmds);
 451
 452        return longest;
 453}
 454
 455static void list_commands(void)
 456{
 457        unsigned int longest = load_command_list();
 458        const char *exec_path = git_exec_path();
 459
 460        if (main_cmds.cnt) {
 461                printf("available git commands in '%s'\n", exec_path);
 462                printf("----------------------------");
 463                mput_char('-', strlen(exec_path));
 464                putchar('\n');
 465                pretty_print_string_list(&main_cmds, longest);
 466                putchar('\n');
 467        }
 468
 469        if (other_cmds.cnt) {
 470                printf("git commands available from elsewhere on your $PATH\n");
 471                printf("---------------------------------------------------\n");
 472                pretty_print_string_list(&other_cmds, longest);
 473                putchar('\n');
 474        }
 475}
 476
 477void list_common_cmds_help(void)
 478{
 479        int i, longest = 0;
 480
 481        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 482                if (longest < strlen(common_cmds[i].name))
 483                        longest = strlen(common_cmds[i].name);
 484        }
 485
 486        puts("The most commonly used git commands are:");
 487        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 488                printf("   %s   ", common_cmds[i].name);
 489                mput_char(' ', longest - strlen(common_cmds[i].name));
 490                puts(common_cmds[i].help);
 491        }
 492}
 493
 494static int is_in_cmdlist(struct cmdnames *c, const char *s)
 495{
 496        int i;
 497        for (i = 0; i < c->cnt; i++)
 498                if (!strcmp(s, c->names[i]->name))
 499                        return 1;
 500        return 0;
 501}
 502
 503static int is_git_command(const char *s)
 504{
 505        load_command_list();
 506        return is_in_cmdlist(&main_cmds, s) ||
 507                is_in_cmdlist(&other_cmds, s);
 508}
 509
 510static const char *cmd_to_page(const char *git_cmd)
 511{
 512        if (!git_cmd)
 513                return "git";
 514        else if (!prefixcmp(git_cmd, "git"))
 515                return git_cmd;
 516        else {
 517                int page_len = strlen(git_cmd) + 4;
 518                char *p = xmalloc(page_len + 1);
 519                strcpy(p, "git-");
 520                strcpy(p + 4, git_cmd);
 521                p[page_len] = 0;
 522                return p;
 523        }
 524}
 525
 526static void setup_man_path(void)
 527{
 528        struct strbuf new_path;
 529        const char *old_path = getenv("MANPATH");
 530
 531        strbuf_init(&new_path, 0);
 532
 533        /* We should always put ':' after our path. If there is no
 534         * old_path, the ':' at the end will let 'man' to try
 535         * system-wide paths after ours to find the manual page. If
 536         * there is old_path, we need ':' as delimiter. */
 537        strbuf_addstr(&new_path, GIT_MAN_PATH);
 538        strbuf_addch(&new_path, ':');
 539        if (old_path)
 540                strbuf_addstr(&new_path, old_path);
 541
 542        setenv("MANPATH", new_path.buf, 1);
 543
 544        strbuf_release(&new_path);
 545}
 546
 547static void exec_viewer(const char *name, const char *page)
 548{
 549        const char *path = get_man_viewer_info(name);
 550
 551        if (!strcasecmp(name, "man"))
 552                exec_man_man(path, page);
 553        else if (!strcasecmp(name, "woman"))
 554                exec_woman_emacs(path, page);
 555        else if (!strcasecmp(name, "konqueror"))
 556                exec_man_konqueror(path, page);
 557        else
 558                warning("'%s': unsupported man viewer.", name);
 559}
 560
 561static void show_man_page(const char *git_cmd)
 562{
 563        struct man_viewer_list *viewer;
 564        const char *page = cmd_to_page(git_cmd);
 565
 566        setup_man_path();
 567        for (viewer = man_viewer_list; viewer; viewer = viewer->next)
 568        {
 569                exec_viewer(viewer->name, page); /* will return when unable */
 570        }
 571        exec_viewer("man", page);
 572        die("no man viewer handled the request");
 573}
 574
 575static void show_info_page(const char *git_cmd)
 576{
 577        const char *page = cmd_to_page(git_cmd);
 578        setenv("INFOPATH", GIT_INFO_PATH, 1);
 579        execlp("info", "info", "gitman", page, NULL);
 580}
 581
 582static void get_html_page_path(struct strbuf *page_path, const char *page)
 583{
 584        struct stat st;
 585
 586        /* Check that we have a git documentation directory. */
 587        if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
 588                die("'%s': not a documentation directory.", GIT_HTML_PATH);
 589
 590        strbuf_init(page_path, 0);
 591        strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
 592}
 593
 594static void show_html_page(const char *git_cmd)
 595{
 596        const char *page = cmd_to_page(git_cmd);
 597        struct strbuf page_path; /* it leaks but we exec bellow */
 598
 599        get_html_page_path(&page_path, page);
 600
 601        execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
 602}
 603
 604void help_unknown_cmd(const char *cmd)
 605{
 606        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 607        exit(1);
 608}
 609
 610int cmd_version(int argc, const char **argv, const char *prefix)
 611{
 612        printf("git version %s\n", git_version_string);
 613        return 0;
 614}
 615
 616int cmd_help(int argc, const char **argv, const char *prefix)
 617{
 618        int nongit;
 619        const char *alias;
 620
 621        setup_git_directory_gently(&nongit);
 622        git_config(git_help_config);
 623
 624        argc = parse_options(argc, argv, builtin_help_options,
 625                        builtin_help_usage, 0);
 626
 627        if (show_all) {
 628                printf("usage: %s\n\n", git_usage_string);
 629                list_commands();
 630                return 0;
 631        }
 632
 633        if (!argv[0]) {
 634                printf("usage: %s\n\n", git_usage_string);
 635                list_common_cmds_help();
 636                return 0;
 637        }
 638
 639        alias = alias_lookup(argv[0]);
 640        if (alias && !is_git_command(argv[0])) {
 641                printf("`git %s' is aliased to `%s'\n", argv[0], alias);
 642                return 0;
 643        }
 644
 645        switch (help_format) {
 646        case HELP_FORMAT_MAN:
 647                show_man_page(argv[0]);
 648                break;
 649        case HELP_FORMAT_INFO:
 650                show_info_page(argv[0]);
 651                break;
 652        case HELP_FORMAT_WEB:
 653                show_html_page(argv[0]);
 654                break;
 655        }
 656
 657        return 0;
 658}