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