help.con commit Documentation: help: describe 'man.viewer' config variable (b5578f3)
   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 const char *man_viewer;
  14
  15enum help_format {
  16        HELP_FORMAT_MAN,
  17        HELP_FORMAT_INFO,
  18        HELP_FORMAT_WEB,
  19};
  20
  21static int show_all = 0;
  22static enum help_format help_format = HELP_FORMAT_MAN;
  23static struct option builtin_help_options[] = {
  24        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
  25        OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
  26        OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
  27                        HELP_FORMAT_WEB),
  28        OPT_SET_INT('i', "info", &help_format, "show info page",
  29                        HELP_FORMAT_INFO),
  30};
  31
  32static const char * const builtin_help_usage[] = {
  33        "git-help [--all] [--man|--web|--info] [command]",
  34        NULL
  35};
  36
  37static enum help_format parse_help_format(const char *format)
  38{
  39        if (!strcmp(format, "man"))
  40                return HELP_FORMAT_MAN;
  41        if (!strcmp(format, "info"))
  42                return HELP_FORMAT_INFO;
  43        if (!strcmp(format, "web") || !strcmp(format, "html"))
  44                return HELP_FORMAT_WEB;
  45        die("unrecognized help format '%s'", format);
  46}
  47
  48static int git_help_config(const char *var, const char *value)
  49{
  50        if (!strcmp(var, "help.format")) {
  51                if (!value)
  52                        return config_error_nonbool(var);
  53                help_format = parse_help_format(value);
  54                return 0;
  55        }
  56        if (!strcmp(var, "man.viewer"))
  57                return git_config_string(&man_viewer, var, value);
  58        return git_default_config(var, value);
  59}
  60
  61/* most GUI terminals set COLUMNS (although some don't export it) */
  62static int term_columns(void)
  63{
  64        char *col_string = getenv("COLUMNS");
  65        int n_cols;
  66
  67        if (col_string && (n_cols = atoi(col_string)) > 0)
  68                return n_cols;
  69
  70#ifdef TIOCGWINSZ
  71        {
  72                struct winsize ws;
  73                if (!ioctl(1, TIOCGWINSZ, &ws)) {
  74                        if (ws.ws_col)
  75                                return ws.ws_col;
  76                }
  77        }
  78#endif
  79
  80        return 80;
  81}
  82
  83static inline void mput_char(char c, unsigned int num)
  84{
  85        while(num--)
  86                putchar(c);
  87}
  88
  89static struct cmdnames {
  90        int alloc;
  91        int cnt;
  92        struct cmdname {
  93                size_t len;
  94                char name[1];
  95        } **names;
  96} main_cmds, other_cmds;
  97
  98static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
  99{
 100        struct cmdname *ent = xmalloc(sizeof(*ent) + len);
 101
 102        ent->len = len;
 103        memcpy(ent->name, name, len);
 104        ent->name[len] = 0;
 105
 106        ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
 107        cmds->names[cmds->cnt++] = ent;
 108}
 109
 110static int cmdname_compare(const void *a_, const void *b_)
 111{
 112        struct cmdname *a = *(struct cmdname **)a_;
 113        struct cmdname *b = *(struct cmdname **)b_;
 114        return strcmp(a->name, b->name);
 115}
 116
 117static void uniq(struct cmdnames *cmds)
 118{
 119        int i, j;
 120
 121        if (!cmds->cnt)
 122                return;
 123
 124        for (i = j = 1; i < cmds->cnt; i++)
 125                if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
 126                        cmds->names[j++] = cmds->names[i];
 127
 128        cmds->cnt = j;
 129}
 130
 131static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
 132{
 133        int ci, cj, ei;
 134        int cmp;
 135
 136        ci = cj = ei = 0;
 137        while (ci < cmds->cnt && ei < excludes->cnt) {
 138                cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
 139                if (cmp < 0)
 140                        cmds->names[cj++] = cmds->names[ci++];
 141                else if (cmp == 0)
 142                        ci++, ei++;
 143                else if (cmp > 0)
 144                        ei++;
 145        }
 146
 147        while (ci < cmds->cnt)
 148                cmds->names[cj++] = cmds->names[ci++];
 149
 150        cmds->cnt = cj;
 151}
 152
 153static void pretty_print_string_list(struct cmdnames *cmds, int longest)
 154{
 155        int cols = 1, rows;
 156        int space = longest + 1; /* min 1 SP between words */
 157        int max_cols = term_columns() - 1; /* don't print *on* the edge */
 158        int i, j;
 159
 160        if (space < max_cols)
 161                cols = max_cols / space;
 162        rows = (cmds->cnt + cols - 1) / cols;
 163
 164        for (i = 0; i < rows; i++) {
 165                printf("  ");
 166
 167                for (j = 0; j < cols; j++) {
 168                        int n = j * rows + i;
 169                        int size = space;
 170                        if (n >= cmds->cnt)
 171                                break;
 172                        if (j == cols-1 || n + rows >= cmds->cnt)
 173                                size = 1;
 174                        printf("%-*s", size, cmds->names[n]->name);
 175                }
 176                putchar('\n');
 177        }
 178}
 179
 180static unsigned int list_commands_in_dir(struct cmdnames *cmds,
 181                                         const char *path)
 182{
 183        unsigned int longest = 0;
 184        const char *prefix = "git-";
 185        int prefix_len = strlen(prefix);
 186        DIR *dir = opendir(path);
 187        struct dirent *de;
 188
 189        if (!dir || chdir(path))
 190                return 0;
 191
 192        while ((de = readdir(dir)) != NULL) {
 193                struct stat st;
 194                int entlen;
 195
 196                if (prefixcmp(de->d_name, prefix))
 197                        continue;
 198
 199                if (stat(de->d_name, &st) || /* stat, not lstat */
 200                    !S_ISREG(st.st_mode) ||
 201                    !(st.st_mode & S_IXUSR))
 202                        continue;
 203
 204                entlen = strlen(de->d_name) - prefix_len;
 205                if (has_extension(de->d_name, ".exe"))
 206                        entlen -= 4;
 207
 208                if (longest < entlen)
 209                        longest = entlen;
 210
 211                add_cmdname(cmds, de->d_name + prefix_len, entlen);
 212        }
 213        closedir(dir);
 214
 215        return longest;
 216}
 217
 218static unsigned int load_command_list(void)
 219{
 220        unsigned int longest = 0;
 221        unsigned int len;
 222        const char *env_path = getenv("PATH");
 223        char *paths, *path, *colon;
 224        const char *exec_path = git_exec_path();
 225
 226        if (exec_path)
 227                longest = list_commands_in_dir(&main_cmds, exec_path);
 228
 229        if (!env_path) {
 230                fprintf(stderr, "PATH not set\n");
 231                exit(1);
 232        }
 233
 234        path = paths = xstrdup(env_path);
 235        while (1) {
 236                if ((colon = strchr(path, ':')))
 237                        *colon = 0;
 238
 239                len = list_commands_in_dir(&other_cmds, path);
 240                if (len > longest)
 241                        longest = len;
 242
 243                if (!colon)
 244                        break;
 245                path = colon + 1;
 246        }
 247        free(paths);
 248
 249        qsort(main_cmds.names, main_cmds.cnt,
 250              sizeof(*main_cmds.names), cmdname_compare);
 251        uniq(&main_cmds);
 252
 253        qsort(other_cmds.names, other_cmds.cnt,
 254              sizeof(*other_cmds.names), cmdname_compare);
 255        uniq(&other_cmds);
 256        exclude_cmds(&other_cmds, &main_cmds);
 257
 258        return longest;
 259}
 260
 261static void list_commands(void)
 262{
 263        unsigned int longest = load_command_list();
 264        const char *exec_path = git_exec_path();
 265
 266        if (main_cmds.cnt) {
 267                printf("available git commands in '%s'\n", exec_path);
 268                printf("----------------------------");
 269                mput_char('-', strlen(exec_path));
 270                putchar('\n');
 271                pretty_print_string_list(&main_cmds, longest);
 272                putchar('\n');
 273        }
 274
 275        if (other_cmds.cnt) {
 276                printf("git commands available from elsewhere on your $PATH\n");
 277                printf("---------------------------------------------------\n");
 278                pretty_print_string_list(&other_cmds, longest);
 279                putchar('\n');
 280        }
 281}
 282
 283void list_common_cmds_help(void)
 284{
 285        int i, longest = 0;
 286
 287        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 288                if (longest < strlen(common_cmds[i].name))
 289                        longest = strlen(common_cmds[i].name);
 290        }
 291
 292        puts("The most commonly used git commands are:");
 293        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 294                printf("   %s   ", common_cmds[i].name);
 295                mput_char(' ', longest - strlen(common_cmds[i].name));
 296                puts(common_cmds[i].help);
 297        }
 298}
 299
 300static int is_in_cmdlist(struct cmdnames *c, const char *s)
 301{
 302        int i;
 303        for (i = 0; i < c->cnt; i++)
 304                if (!strcmp(s, c->names[i]->name))
 305                        return 1;
 306        return 0;
 307}
 308
 309static int is_git_command(const char *s)
 310{
 311        load_command_list();
 312        return is_in_cmdlist(&main_cmds, s) ||
 313                is_in_cmdlist(&other_cmds, s);
 314}
 315
 316static const char *cmd_to_page(const char *git_cmd)
 317{
 318        if (!git_cmd)
 319                return "git";
 320        else if (!prefixcmp(git_cmd, "git"))
 321                return git_cmd;
 322        else {
 323                int page_len = strlen(git_cmd) + 4;
 324                char *p = xmalloc(page_len + 1);
 325                strcpy(p, "git-");
 326                strcpy(p + 4, git_cmd);
 327                p[page_len] = 0;
 328                return p;
 329        }
 330}
 331
 332static void setup_man_path(void)
 333{
 334        struct strbuf new_path;
 335        const char *old_path = getenv("MANPATH");
 336
 337        strbuf_init(&new_path, 0);
 338
 339        /* We should always put ':' after our path. If there is no
 340         * old_path, the ':' at the end will let 'man' to try
 341         * system-wide paths after ours to find the manual page. If
 342         * there is old_path, we need ':' as delimiter. */
 343        strbuf_addstr(&new_path, GIT_MAN_PATH);
 344        strbuf_addch(&new_path, ':');
 345        if (old_path)
 346                strbuf_addstr(&new_path, old_path);
 347
 348        setenv("MANPATH", new_path.buf, 1);
 349
 350        strbuf_release(&new_path);
 351}
 352
 353static int check_emacsclient_version(void)
 354{
 355        struct strbuf buffer = STRBUF_INIT;
 356        struct child_process ec_process;
 357        const char *argv_ec[] = { "emacsclient", "--version", NULL };
 358        int version;
 359
 360        /* emacsclient prints its version number on stderr */
 361        memset(&ec_process, 0, sizeof(ec_process));
 362        ec_process.argv = argv_ec;
 363        ec_process.err = -1;
 364        ec_process.stdout_to_stderr = 1;
 365        if (start_command(&ec_process)) {
 366                fprintf(stderr, "Failed to start emacsclient.\n");
 367                return -1;
 368        }
 369        strbuf_read(&buffer, ec_process.err, 20);
 370        close(ec_process.err);
 371
 372        /*
 373         * Don't bother checking return value, because "emacsclient --version"
 374         * seems to always exits with code 1.
 375         */
 376        finish_command(&ec_process);
 377
 378        if (prefixcmp(buffer.buf, "emacsclient")) {
 379                fprintf(stderr, "Failed to parse emacsclient version.\n");
 380                strbuf_release(&buffer);
 381                return -1;
 382        }
 383
 384        strbuf_remove(&buffer, 0, strlen("emacsclient"));
 385        version = atoi(buffer.buf);
 386
 387        if (version < 22) {
 388                fprintf(stderr,
 389                        "emacsclient version '%d' too old (< 22).\n",
 390                        version);
 391                strbuf_release(&buffer);
 392                return -1;
 393        }
 394
 395        strbuf_release(&buffer);
 396        return 0;
 397}
 398
 399static void exec_woman_emacs(const char *page)
 400{
 401        if (!check_emacsclient_version()) {
 402                /* This works only with emacsclient version >= 22. */
 403                struct strbuf man_page = STRBUF_INIT;
 404                strbuf_addf(&man_page, "(woman \"%s\")", page);
 405                execlp("emacsclient", "emacsclient", "-e", man_page.buf, NULL);
 406        } else
 407                execlp("man", "man", page, NULL);
 408}
 409
 410static void exec_man_konqueror(const char *page)
 411{
 412        const char *display = getenv("DISPLAY");
 413        if (display && *display) {
 414                struct strbuf man_page = STRBUF_INIT;
 415                strbuf_addf(&man_page, "man:%s(1)", page);
 416                execlp("kfmclient", "kfmclient", "newTab", man_page.buf, NULL);
 417        } else
 418                execlp("man", "man", page, NULL);
 419}
 420
 421static void show_man_page(const char *git_cmd)
 422{
 423        const char *page = cmd_to_page(git_cmd);
 424        setup_man_path();
 425        if (!man_viewer || !strcmp(man_viewer, "man"))
 426                execlp("man", "man", page, NULL);
 427        if (!strcmp(man_viewer, "woman"))
 428                exec_woman_emacs(page);
 429        if (!strcmp(man_viewer, "konqueror"))
 430                exec_man_konqueror(page);
 431        die("'%s': unsupported man viewer.", man_viewer);
 432}
 433
 434static void show_info_page(const char *git_cmd)
 435{
 436        const char *page = cmd_to_page(git_cmd);
 437        setenv("INFOPATH", GIT_INFO_PATH, 1);
 438        execlp("info", "info", "gitman", page, NULL);
 439}
 440
 441static void get_html_page_path(struct strbuf *page_path, const char *page)
 442{
 443        struct stat st;
 444
 445        /* Check that we have a git documentation directory. */
 446        if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
 447                die("'%s': not a documentation directory.", GIT_HTML_PATH);
 448
 449        strbuf_init(page_path, 0);
 450        strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
 451}
 452
 453static void show_html_page(const char *git_cmd)
 454{
 455        const char *page = cmd_to_page(git_cmd);
 456        struct strbuf page_path; /* it leaks but we exec bellow */
 457
 458        get_html_page_path(&page_path, page);
 459
 460        execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
 461}
 462
 463void help_unknown_cmd(const char *cmd)
 464{
 465        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 466        exit(1);
 467}
 468
 469int cmd_version(int argc, const char **argv, const char *prefix)
 470{
 471        printf("git version %s\n", git_version_string);
 472        return 0;
 473}
 474
 475int cmd_help(int argc, const char **argv, const char *prefix)
 476{
 477        int nongit;
 478        const char *alias;
 479
 480        setup_git_directory_gently(&nongit);
 481        git_config(git_help_config);
 482
 483        argc = parse_options(argc, argv, builtin_help_options,
 484                        builtin_help_usage, 0);
 485
 486        if (show_all) {
 487                printf("usage: %s\n\n", git_usage_string);
 488                list_commands();
 489                return 0;
 490        }
 491
 492        if (!argv[0]) {
 493                printf("usage: %s\n\n", git_usage_string);
 494                list_common_cmds_help();
 495                return 0;
 496        }
 497
 498        alias = alias_lookup(argv[0]);
 499        if (alias && !is_git_command(argv[0])) {
 500                printf("`git %s' is aliased to `%s'\n", argv[0], alias);
 501                return 0;
 502        }
 503
 504        switch (help_format) {
 505        case HELP_FORMAT_MAN:
 506                show_man_page(argv[0]);
 507                break;
 508        case HELP_FORMAT_INFO:
 509                show_info_page(argv[0]);
 510                break;
 511        case HELP_FORMAT_WEB:
 512                show_html_page(argv[0]);
 513                break;
 514        }
 515
 516        return 0;
 517}