git.con commit Make "git help" react to window size correctly (ea77e67)
   1#include <stdio.h>
   2#include <sys/types.h>
   3#include <sys/stat.h>
   4#include <dirent.h>
   5#include <unistd.h>
   6#include <stdlib.h>
   7#include <string.h>
   8#include <errno.h>
   9#include <limits.h>
  10#include <stdarg.h>
  11#include <sys/ioctl.h>
  12#include "git-compat-util.h"
  13
  14#ifndef PATH_MAX
  15# define PATH_MAX 4096
  16#endif
  17
  18static const char git_usage[] =
  19        "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
  20
  21/* most gui terms set COLUMNS (although some don't export it) */
  22static int term_columns(void)
  23{
  24        char *col_string = getenv("COLUMNS");
  25        int n_cols = 0;
  26
  27        if (col_string && (n_cols = atoi(col_string)) > 0)
  28                return n_cols;
  29
  30#ifdef TIOCGWINSZ
  31        {
  32                struct winsize ws;
  33                if (!ioctl(1, TIOCGWINSZ, &ws)) {
  34                        if (ws.ws_col)
  35                                return ws.ws_col;
  36                }
  37        }
  38#endif
  39
  40        return 80;
  41}
  42
  43static void oom(void)
  44{
  45        fprintf(stderr, "git: out of memory\n");
  46        exit(1);
  47}
  48
  49static inline void mput_char(char c, unsigned int num)
  50{
  51        while(num--)
  52                putchar(c);
  53}
  54
  55static struct cmdname {
  56        size_t len;
  57        char name[1];
  58} **cmdname;
  59static int cmdname_alloc, cmdname_cnt;
  60
  61static void add_cmdname(const char *name, int len)
  62{
  63        struct cmdname *ent;
  64        if (cmdname_alloc <= cmdname_cnt) {
  65                cmdname_alloc = cmdname_alloc + 200;
  66                cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
  67                if (!cmdname)
  68                        oom();
  69        }
  70        ent = malloc(sizeof(*ent) + len);
  71        if (!ent)
  72                oom();
  73        ent->len = len;
  74        memcpy(ent->name, name, len);
  75        ent->name[len] = 0;
  76        cmdname[cmdname_cnt++] = ent;
  77}
  78
  79static int cmdname_compare(const void *a_, const void *b_)
  80{
  81        struct cmdname *a = *(struct cmdname **)a_;
  82        struct cmdname *b = *(struct cmdname **)b_;
  83        return strcmp(a->name, b->name);
  84}
  85
  86static void pretty_print_string_list(struct cmdname **cmdname, int longest)
  87{
  88        int cols = 1;
  89        int space = longest + 1; /* min 1 SP between words */
  90        int max_cols = term_columns() - 1; /* don't print *on* the edge */
  91        int i;
  92
  93        if (space < max_cols)
  94                cols = max_cols / space;
  95
  96        qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
  97
  98        for (i = 0; i < cmdname_cnt; ) {
  99                int c;
 100                printf("  ");
 101
 102                for (c = cols; c && i < cmdname_cnt; i++) {
 103                        printf("%s", cmdname[i]->name);
 104
 105                        if (--c)
 106                                mput_char(' ', space - cmdname[i]->len);
 107                }
 108                putchar('\n');
 109        }
 110}
 111
 112static void list_commands(const char *exec_path, const char *pattern)
 113{
 114        unsigned int longest = 0;
 115        char path[PATH_MAX];
 116        int dirlen;
 117        DIR *dir = opendir(exec_path);
 118        struct dirent *de;
 119
 120        if (!dir) {
 121                fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
 122                exit(1);
 123        }
 124
 125        dirlen = strlen(exec_path);
 126        if (PATH_MAX - 20 < dirlen) {
 127                fprintf(stderr, "git: insanely long exec-path '%s'\n",
 128                        exec_path);
 129                exit(1);
 130        }
 131
 132        memcpy(path, exec_path, dirlen);
 133        path[dirlen++] = '/';
 134
 135        while ((de = readdir(dir)) != NULL) {
 136                struct stat st;
 137                int entlen;
 138
 139                if (strncmp(de->d_name, "git-", 4))
 140                        continue;
 141                strcpy(path+dirlen, de->d_name);
 142                if (stat(path, &st) || /* stat, not lstat */
 143                    !S_ISREG(st.st_mode) ||
 144                    !(st.st_mode & S_IXUSR))
 145                        continue;
 146
 147                entlen = strlen(de->d_name);
 148                if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
 149                        entlen -= 4;
 150
 151                if (longest < entlen)
 152                        longest = entlen;
 153
 154                add_cmdname(de->d_name + 4, entlen-4);
 155        }
 156        closedir(dir);
 157
 158        printf("git commands available in '%s'\n", exec_path);
 159        printf("----------------------------");
 160        mput_char('-', strlen(exec_path));
 161        putchar('\n');
 162        pretty_print_string_list(cmdname, longest - 4);
 163        putchar('\n');
 164}
 165
 166#ifdef __GNUC__
 167static void cmd_usage(const char *exec_path, const char *fmt, ...)
 168        __attribute__((__format__(__printf__, 2, 3), __noreturn__));
 169#endif
 170static void cmd_usage(const char *exec_path, const char *fmt, ...)
 171{
 172        if (fmt) {
 173                va_list ap;
 174
 175                va_start(ap, fmt);
 176                printf("git: ");
 177                vprintf(fmt, ap);
 178                va_end(ap);
 179                putchar('\n');
 180        }
 181        else
 182                puts(git_usage);
 183
 184        putchar('\n');
 185
 186        if(exec_path)
 187                list_commands(exec_path, "git-*");
 188
 189        exit(1);
 190}
 191
 192static void prepend_to_path(const char *dir, int len)
 193{
 194        char *path, *old_path = getenv("PATH");
 195        int path_len = len;
 196
 197        if (!old_path)
 198                old_path = "/usr/local/bin:/usr/bin:/bin";
 199
 200        path_len = len + strlen(old_path) + 1;
 201
 202        path = malloc(path_len + 1);
 203
 204        memcpy(path, dir, len);
 205        path[len] = ':';
 206        memcpy(path + len + 1, old_path, path_len - len);
 207
 208        setenv("PATH", path, 1);
 209}
 210
 211static void show_man_page(char *git_cmd)
 212{
 213        char *page;
 214
 215        if (!strncmp(git_cmd, "git", 3))
 216                page = git_cmd;
 217        else {
 218                int page_len = strlen(git_cmd) + 4;
 219
 220                page = malloc(page_len + 1);
 221                strcpy(page, "git-");
 222                strcpy(page + 4, git_cmd);
 223                page[page_len] = 0;
 224        }
 225
 226        execlp("man", "man", page, NULL);
 227}
 228
 229int main(int argc, char **argv, char **envp)
 230{
 231        char git_command[PATH_MAX + 1];
 232        char wd[PATH_MAX + 1];
 233        int i, len, show_help = 0;
 234        char *exec_path = getenv("GIT_EXEC_PATH");
 235
 236        getcwd(wd, PATH_MAX);
 237
 238        if (!exec_path)
 239                exec_path = GIT_EXEC_PATH;
 240
 241        for (i = 1; i < argc; i++) {
 242                char *arg = argv[i];
 243
 244                if (strncmp(arg, "--", 2))
 245                        break;
 246
 247                arg += 2;
 248
 249                if (!strncmp(arg, "exec-path", 9)) {
 250                        arg += 9;
 251                        if (*arg == '=')
 252                                exec_path = arg + 1;
 253                        else {
 254                                puts(exec_path);
 255                                exit(0);
 256                        }
 257                }
 258                else if (!strcmp(arg, "version")) {
 259                        printf("git version %s\n", GIT_VERSION);
 260                        exit(0);
 261                }
 262                else if (!strcmp(arg, "help"))
 263                        show_help = 1;
 264                else if (!show_help)
 265                        cmd_usage(NULL, NULL);
 266        }
 267
 268        if (i >= argc || show_help) {
 269                if (i >= argc)
 270                        cmd_usage(exec_path, NULL);
 271
 272                show_man_page(argv[i]);
 273        }
 274
 275        if (*exec_path != '/') {
 276                if (!getcwd(git_command, sizeof(git_command))) {
 277                        fprintf(stderr,
 278                                "git: cannot determine current directory");
 279                        exit(1);
 280                }
 281                len = strlen(git_command);
 282
 283                /* Trivial cleanup */
 284                while (!strncmp(exec_path, "./", 2)) {
 285                        exec_path += 2;
 286                        while (*exec_path == '/')
 287                                exec_path++;
 288                }
 289                snprintf(git_command + len, sizeof(git_command) - len,
 290                         "/%s", exec_path);
 291        }
 292        else
 293                strcpy(git_command, exec_path);
 294        len = strlen(git_command);
 295        prepend_to_path(git_command, len);
 296
 297        len += snprintf(git_command + len, sizeof(git_command) - len,
 298                        "/git-%s", argv[i]);
 299        if (sizeof(git_command) <= len) {
 300                fprintf(stderr, "git: command name given is too long.\n");
 301                exit(1);
 302        }
 303
 304        /* execve() can only ever return if it fails */
 305        execve(git_command, &argv[i], envp);
 306
 307        if (errno == ENOENT)
 308                cmd_usage(exec_path, "'%s' is not a git-command", argv[i]);
 309
 310        fprintf(stderr, "Failed to run command '%s': %s\n",
 311                git_command, strerror(errno));
 312
 313        return 1;
 314}