help.con commit Merge branch 'maint' (3e4bb08)
   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        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        puts("(use 'git help -a' to get a list of all installed git commands)");
 241}
 242
 243static void show_man_page(const char *git_cmd)
 244{
 245        const char *page;
 246
 247        if (!prefixcmp(git_cmd, "git"))
 248                page = git_cmd;
 249        else {
 250                int page_len = strlen(git_cmd) + 4;
 251                char *p = xmalloc(page_len + 1);
 252                strcpy(p, "git-");
 253                strcpy(p + 4, git_cmd);
 254                p[page_len] = 0;
 255                page = p;
 256        }
 257
 258        execlp("man", "man", page, NULL);
 259}
 260
 261void help_unknown_cmd(const char *cmd)
 262{
 263        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 264        exit(1);
 265}
 266
 267int cmd_version(int argc, const char **argv, const char *prefix)
 268{
 269        printf("git version %s\n", git_version_string);
 270        return 0;
 271}
 272
 273int cmd_help(int argc, const char **argv, const char *prefix)
 274{
 275        const char *help_cmd = argc > 1 ? argv[1] : NULL;
 276
 277        if (!help_cmd) {
 278                printf("usage: %s\n\n", git_usage_string);
 279                list_common_cmds_help();
 280                exit(0);
 281        }
 282
 283        else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
 284                printf("usage: %s\n\n", git_usage_string);
 285                list_commands();
 286                exit(0);
 287        }
 288
 289        else
 290                show_man_page(help_cmd);
 291
 292        return 0;
 293}