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