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