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