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