help.con commit Merge branch 'maint' (c9c3470)
   1/*
   2 * builtin-help.c
   3 *
   4 * Builtin help-related commands (help, usage, version)
   5 */
   6#include <sys/ioctl.h>
   7#include "cache.h"
   8#include "builtin.h"
   9#include "exec_cmd.h"
  10#include "common-cmds.h"
  11
  12
  13/* most GUI terminals set COLUMNS (although some don't export it) */
  14static int term_columns(void)
  15{
  16        char *col_string = getenv("COLUMNS");
  17        int n_cols = 0;
  18
  19        if (col_string && (n_cols = atoi(col_string)) > 0)
  20                return n_cols;
  21
  22#ifdef TIOCGWINSZ
  23        {
  24                struct winsize ws;
  25                if (!ioctl(1, TIOCGWINSZ, &ws)) {
  26                        if (ws.ws_col)
  27                                return ws.ws_col;
  28                }
  29        }
  30#endif
  31
  32        return 80;
  33}
  34
  35static void oom(void)
  36{
  37        fprintf(stderr, "git: out of memory\n");
  38        exit(1);
  39}
  40
  41static inline void mput_char(char c, unsigned int num)
  42{
  43        while(num--)
  44                putchar(c);
  45}
  46
  47static struct cmdname {
  48        size_t len;
  49        char name[1];
  50} **cmdname;
  51static int cmdname_alloc, cmdname_cnt;
  52
  53static void add_cmdname(const char *name, int len)
  54{
  55        struct cmdname *ent;
  56        if (cmdname_alloc <= cmdname_cnt) {
  57                cmdname_alloc = cmdname_alloc + 200;
  58                cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
  59                if (!cmdname)
  60                        oom();
  61        }
  62        ent = malloc(sizeof(*ent) + len);
  63        if (!ent)
  64                oom();
  65        ent->len = len;
  66        memcpy(ent->name, name, len);
  67        ent->name[len] = 0;
  68        cmdname[cmdname_cnt++] = ent;
  69}
  70
  71static int cmdname_compare(const void *a_, const void *b_)
  72{
  73        struct cmdname *a = *(struct cmdname **)a_;
  74        struct cmdname *b = *(struct cmdname **)b_;
  75        return strcmp(a->name, b->name);
  76}
  77
  78static void pretty_print_string_list(struct cmdname **cmdname, int longest)
  79{
  80        int cols = 1, rows;
  81        int space = longest + 1; /* min 1 SP between words */
  82        int max_cols = term_columns() - 1; /* don't print *on* the edge */
  83        int i, j;
  84
  85        if (space < max_cols)
  86                cols = max_cols / space;
  87        rows = (cmdname_cnt + cols - 1) / cols;
  88
  89        qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
  90
  91        for (i = 0; i < rows; i++) {
  92                printf("  ");
  93
  94                for (j = 0; j < cols; j++) {
  95                        int n = j * rows + i;
  96                        int size = space;
  97                        if (n >= cmdname_cnt)
  98                                break;
  99                        if (j == cols-1 || n + rows >= cmdname_cnt)
 100                                size = 1;
 101                        printf("%-*s", size, cmdname[n]->name);
 102                }
 103                putchar('\n');
 104        }
 105}
 106
 107static void list_commands(const char *exec_path, const char *pattern)
 108{
 109        unsigned int longest = 0;
 110        char path[PATH_MAX];
 111        int dirlen;
 112        DIR *dir = opendir(exec_path);
 113        struct dirent *de;
 114
 115        if (!dir) {
 116                fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
 117                exit(1);
 118        }
 119
 120        dirlen = strlen(exec_path);
 121        if (PATH_MAX - 20 < dirlen) {
 122                fprintf(stderr, "git: insanely long exec-path '%s'\n",
 123                        exec_path);
 124                exit(1);
 125        }
 126
 127        memcpy(path, exec_path, dirlen);
 128        path[dirlen++] = '/';
 129
 130        while ((de = readdir(dir)) != NULL) {
 131                struct stat st;
 132                int entlen;
 133
 134                if (strncmp(de->d_name, "git-", 4))
 135                        continue;
 136                strcpy(path+dirlen, de->d_name);
 137                if (stat(path, &st) || /* stat, not lstat */
 138                    !S_ISREG(st.st_mode) ||
 139                    !(st.st_mode & S_IXUSR))
 140                        continue;
 141
 142                entlen = strlen(de->d_name);
 143                if (has_extension(de->d_name, ".exe"))
 144                        entlen -= 4;
 145
 146                if (longest < entlen)
 147                        longest = entlen;
 148
 149                add_cmdname(de->d_name + 4, entlen-4);
 150        }
 151        closedir(dir);
 152
 153        printf("git commands available in '%s'\n", exec_path);
 154        printf("----------------------------");
 155        mput_char('-', strlen(exec_path));
 156        putchar('\n');
 157        pretty_print_string_list(cmdname, longest - 4);
 158        putchar('\n');
 159}
 160
 161static void list_common_cmds_help(void)
 162{
 163        int i, longest = 0;
 164
 165        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 166                if (longest < strlen(common_cmds[i].name))
 167                        longest = strlen(common_cmds[i].name);
 168        }
 169
 170        puts("The most commonly used git commands are:");
 171        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
 172                printf("    %s", common_cmds[i].name);
 173                mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
 174                puts(common_cmds[i].help);
 175        }
 176        puts("(use 'git help -a' to get a list of all installed git commands)");
 177}
 178
 179static void show_man_page(const char *git_cmd)
 180{
 181        const char *page;
 182
 183        if (!strncmp(git_cmd, "git", 3))
 184                page = git_cmd;
 185        else {
 186                int page_len = strlen(git_cmd) + 4;
 187                char *p = malloc(page_len + 1);
 188                strcpy(p, "git-");
 189                strcpy(p + 4, git_cmd);
 190                p[page_len] = 0;
 191                page = p;
 192        }
 193
 194        execlp("man", "man", page, NULL);
 195}
 196
 197void help_unknown_cmd(const char *cmd)
 198{
 199        printf("git: '%s' is not a git-command\n\n", cmd);
 200        list_common_cmds_help();
 201        exit(1);
 202}
 203
 204int cmd_version(int argc, const char **argv, const char *prefix)
 205{
 206        printf("git version %s\n", git_version_string);
 207        return 0;
 208}
 209
 210int cmd_help(int argc, const char **argv, const char *prefix)
 211{
 212        const char *help_cmd = argc > 1 ? argv[1] : NULL;
 213        const char *exec_path = git_exec_path();
 214
 215        if (!help_cmd) {
 216                printf("usage: %s\n\n", git_usage_string);
 217                list_common_cmds_help();
 218                exit(1);
 219        }
 220
 221        else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
 222                printf("usage: %s\n\n", git_usage_string);
 223                if(exec_path)
 224                        list_commands(exec_path, "git-*");
 225                exit(1);
 226        }
 227
 228        else
 229                show_man_page(help_cmd);
 230
 231        return 0;
 232}
 233
 234