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