help.con commit Merge branch 'jc/diff-relative' (860cc3a)
   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
  11static const char *help_default_format;
  12
  13static enum help_format {
  14        man_format,
  15        info_format,
  16        web_format,
  17} help_format = man_format;
  18
  19static void parse_help_format(const char *format)
  20{
  21        if (!format) {
  22                help_format = man_format;
  23                return;
  24        }
  25        if (!strcmp(format, "man")) {
  26                help_format = man_format;
  27                return;
  28        }
  29        if (!strcmp(format, "info")) {
  30                help_format = info_format;
  31                return;
  32        }
  33        if (!strcmp(format, "web") || !strcmp(format, "html")) {
  34                help_format = web_format;
  35                return;
  36        }
  37        die("unrecognized help format '%s'", format);
  38}
  39
  40static int git_help_config(const char *var, const char *value)
  41{
  42        if (!strcmp(var, "help.format"))
  43                return git_config_string(&help_default_format, var, value);
  44        return git_default_config(var, value);
  45}
  46
  47/* most GUI terminals set COLUMNS (although some don't export it) */
  48static int term_columns(void)
  49{
  50        char *col_string = getenv("COLUMNS");
  51        int n_cols;
  52
  53        if (col_string && (n_cols = atoi(col_string)) > 0)
  54                return n_cols;
  55
  56#ifdef TIOCGWINSZ
  57        {
  58                struct winsize ws;
  59                if (!ioctl(1, TIOCGWINSZ, &ws)) {
  60                        if (ws.ws_col)
  61                                return ws.ws_col;
  62                }
  63        }
  64#endif
  65
  66        return 80;
  67}
  68
  69static inline void mput_char(char c, unsigned int num)
  70{
  71        while(num--)
  72                putchar(c);
  73}
  74
  75static struct cmdnames {
  76        int alloc;
  77        int cnt;
  78        struct cmdname {
  79                size_t len;
  80                char name[1];
  81        } **names;
  82} main_cmds, other_cmds;
  83
  84static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
  85{
  86        struct cmdname *ent = xmalloc(sizeof(*ent) + len);
  87
  88        ent->len = len;
  89        memcpy(ent->name, name, len);
  90        ent->name[len] = 0;
  91
  92        ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
  93        cmds->names[cmds->cnt++] = ent;
  94}
  95
  96static int cmdname_compare(const void *a_, const void *b_)
  97{
  98        struct cmdname *a = *(struct cmdname **)a_;
  99        struct cmdname *b = *(struct cmdname **)b_;
 100        return strcmp(a->name, b->name);
 101}
 102
 103static void uniq(struct cmdnames *cmds)
 104{
 105        int i, j;
 106
 107        if (!cmds->cnt)
 108                return;
 109
 110        for (i = j = 1; i < cmds->cnt; i++)
 111                if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
 112                        cmds->names[j++] = cmds->names[i];
 113
 114        cmds->cnt = j;
 115}
 116
 117static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
 118{
 119        int ci, cj, ei;
 120        int cmp;
 121
 122        ci = cj = ei = 0;
 123        while (ci < cmds->cnt && ei < excludes->cnt) {
 124                cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
 125                if (cmp < 0)
 126                        cmds->names[cj++] = cmds->names[ci++];
 127                else if (cmp == 0)
 128                        ci++, ei++;
 129                else if (cmp > 0)
 130                        ei++;
 131        }
 132
 133        while (ci < cmds->cnt)
 134                cmds->names[cj++] = cmds->names[ci++];
 135
 136        cmds->cnt = cj;
 137}
 138
 139static void pretty_print_string_list(struct cmdnames *cmds, int longest)
 140{
 141        int cols = 1, rows;
 142        int space = longest + 1; /* min 1 SP between words */
 143        int max_cols = term_columns() - 1; /* don't print *on* the edge */
 144        int i, j;
 145
 146        if (space < max_cols)
 147                cols = max_cols / space;
 148        rows = (cmds->cnt + cols - 1) / cols;
 149
 150        for (i = 0; i < rows; i++) {
 151                printf("  ");
 152
 153                for (j = 0; j < cols; j++) {
 154                        int n = j * rows + i;
 155                        int size = space;
 156                        if (n >= cmds->cnt)
 157                                break;
 158                        if (j == cols-1 || n + rows >= cmds->cnt)
 159                                size = 1;
 160                        printf("%-*s", size, cmds->names[n]->name);
 161                }
 162                putchar('\n');
 163        }
 164}
 165
 166static unsigned int list_commands_in_dir(struct cmdnames *cmds,
 167                                         const char *path)
 168{
 169        unsigned int longest = 0;
 170        const char *prefix = "git-";
 171        int prefix_len = strlen(prefix);
 172        DIR *dir = opendir(path);
 173        struct dirent *de;
 174
 175        if (!dir || chdir(path))
 176                return 0;
 177
 178        while ((de = readdir(dir)) != NULL) {
 179                struct stat st;
 180                int entlen;
 181
 182                if (prefixcmp(de->d_name, prefix))
 183                        continue;
 184
 185                if (stat(de->d_name, &st) || /* stat, not lstat */
 186                    !S_ISREG(st.st_mode) ||
 187                    !(st.st_mode & S_IXUSR))
 188                        continue;
 189
 190                entlen = strlen(de->d_name) - prefix_len;
 191                if (has_extension(de->d_name, ".exe"))
 192                        entlen -= 4;
 193
 194                if (longest < entlen)
 195                        longest = entlen;
 196
 197                add_cmdname(cmds, de->d_name + prefix_len, entlen);
 198        }
 199        closedir(dir);
 200
 201        return longest;
 202}
 203
 204static void list_commands(void)
 205{
 206        unsigned int longest = 0;
 207        unsigned int len;
 208        const char *env_path = getenv("PATH");
 209        char *paths, *path, *colon;
 210        const char *exec_path = git_exec_path();
 211
 212        if (exec_path)
 213                longest = list_commands_in_dir(&main_cmds, exec_path);
 214
 215        if (!env_path) {
 216                fprintf(stderr, "PATH not set\n");
 217                exit(1);
 218        }
 219
 220        path = paths = xstrdup(env_path);
 221        while (1) {
 222                if ((colon = strchr(path, ':')))
 223                        *colon = 0;
 224
 225                len = list_commands_in_dir(&other_cmds, path);
 226                if (len > longest)
 227                        longest = len;
 228
 229                if (!colon)
 230                        break;
 231                path = colon + 1;
 232        }
 233        free(paths);
 234
 235        qsort(main_cmds.names, main_cmds.cnt,
 236              sizeof(*main_cmds.names), cmdname_compare);
 237        uniq(&main_cmds);
 238
 239        qsort(other_cmds.names, other_cmds.cnt,
 240              sizeof(*other_cmds.names), cmdname_compare);
 241        uniq(&other_cmds);
 242        exclude_cmds(&other_cmds, &main_cmds);
 243
 244        if (main_cmds.cnt) {
 245                printf("available git commands in '%s'\n", exec_path);
 246                printf("----------------------------");
 247                mput_char('-', strlen(exec_path));
 248                putchar('\n');
 249                pretty_print_string_list(&main_cmds, longest);
 250                putchar('\n');
 251        }
 252
 253        if (other_cmds.cnt) {
 254                printf("git commands available from elsewhere on your $PATH\n");
 255                printf("---------------------------------------------------\n");
 256                pretty_print_string_list(&other_cmds, longest);
 257                putchar('\n');
 258        }
 259}
 260
 261void list_common_cmds_help(void)
 262{
 263        int i, longest = 0;
 264
 265        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 266                if (longest < strlen(common_cmds[i].name))
 267                        longest = strlen(common_cmds[i].name);
 268        }
 269
 270        puts("The most commonly used git commands are:");
 271        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 272                printf("   %s   ", common_cmds[i].name);
 273                mput_char(' ', longest - strlen(common_cmds[i].name));
 274                puts(common_cmds[i].help);
 275        }
 276}
 277
 278static const char *cmd_to_page(const char *git_cmd)
 279{
 280        if (!git_cmd)
 281                return "git";
 282        else if (!prefixcmp(git_cmd, "git"))
 283                return git_cmd;
 284        else {
 285                int page_len = strlen(git_cmd) + 4;
 286                char *p = xmalloc(page_len + 1);
 287                strcpy(p, "git-");
 288                strcpy(p + 4, git_cmd);
 289                p[page_len] = 0;
 290                return p;
 291        }
 292}
 293
 294static void setup_man_path(void)
 295{
 296        struct strbuf new_path;
 297        const char *old_path = getenv("MANPATH");
 298
 299        strbuf_init(&new_path, 0);
 300
 301        /* We should always put ':' after our path. If there is no
 302         * old_path, the ':' at the end will let 'man' to try
 303         * system-wide paths after ours to find the manual page. If
 304         * there is old_path, we need ':' as delimiter. */
 305        strbuf_addstr(&new_path, GIT_MAN_PATH);
 306        strbuf_addch(&new_path, ':');
 307        if (old_path)
 308                strbuf_addstr(&new_path, old_path);
 309
 310        setenv("MANPATH", new_path.buf, 1);
 311
 312        strbuf_release(&new_path);
 313}
 314
 315static void show_man_page(const char *git_cmd)
 316{
 317        const char *page = cmd_to_page(git_cmd);
 318        setup_man_path();
 319        execlp("man", "man", page, NULL);
 320}
 321
 322static void show_info_page(const char *git_cmd)
 323{
 324        const char *page = cmd_to_page(git_cmd);
 325        setenv("INFOPATH", GIT_INFO_PATH, 1);
 326        execlp("info", "info", "gitman", page, NULL);
 327}
 328
 329static void get_html_page_path(struct strbuf *page_path, const char *page)
 330{
 331        struct stat st;
 332
 333        /* Check that we have a git documentation directory. */
 334        if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
 335                die("'%s': not a documentation directory.", GIT_HTML_PATH);
 336
 337        strbuf_init(page_path, 0);
 338        strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
 339}
 340
 341static void show_html_page(const char *git_cmd)
 342{
 343        const char *page = cmd_to_page(git_cmd);
 344        struct strbuf page_path; /* it leaks but we exec bellow */
 345
 346        get_html_page_path(&page_path, page);
 347
 348        execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
 349}
 350
 351void help_unknown_cmd(const char *cmd)
 352{
 353        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 354        exit(1);
 355}
 356
 357int cmd_version(int argc, const char **argv, const char *prefix)
 358{
 359        printf("git version %s\n", git_version_string);
 360        return 0;
 361}
 362
 363int cmd_help(int argc, const char **argv, const char *prefix)
 364{
 365        const char *help_cmd = argv[1];
 366
 367        if (argc < 2) {
 368                printf("usage: %s\n\n", git_usage_string);
 369                list_common_cmds_help();
 370                exit(0);
 371        }
 372
 373        if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
 374                printf("usage: %s\n\n", git_usage_string);
 375                list_commands();
 376        }
 377
 378        else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) {
 379                show_html_page(argc > 2 ? argv[2] : NULL);
 380        }
 381
 382        else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) {
 383                show_info_page(argc > 2 ? argv[2] : NULL);
 384        }
 385
 386        else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) {
 387                show_man_page(argc > 2 ? argv[2] : NULL);
 388        }
 389
 390        else {
 391                int nongit;
 392
 393                setup_git_directory_gently(&nongit);
 394                git_config(git_help_config);
 395                if (help_default_format)
 396                        parse_help_format(help_default_format);
 397
 398                switch (help_format) {
 399                case man_format:
 400                        show_man_page(help_cmd);
 401                        break;
 402                case info_format:
 403                        show_info_page(help_cmd);
 404                        break;
 405                case web_format:
 406                        show_html_page(help_cmd);
 407                        break;
 408                }
 409        }
 410
 411        return 0;
 412}