git.con commit Add compat/setenv.c, use in git.c. (e40b61f)
   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
  12#ifndef PATH_MAX
  13# define PATH_MAX 4096
  14#endif
  15
  16#ifdef NO_SETENV
  17extern int gitsetenv(char *name, char *value, int overwrite);
  18#endif
  19
  20static const char git_usage[] =
  21        "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
  22
  23/* most gui terms set COLUMNS (although some don't export it) */
  24static int term_columns(void)
  25{
  26        char *col_string = getenv("COLUMNS");
  27        int n_cols = 0;
  28
  29        if (col_string && (n_cols = atoi(col_string)) > 0)
  30                return n_cols;
  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;
  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;
  84
  85        if (space < max_cols)
  86                cols = max_cols / space;
  87
  88        qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
  89
  90        for (i = 0; i < cmdname_cnt; ) {
  91                int c;
  92                printf("  ");
  93
  94                for (c = cols; c && i < cmdname_cnt; i++) {
  95                        printf("%s", cmdname[i]->name);
  96
  97                        if (--c)
  98                                mput_char(' ', space - cmdname[i]->len);
  99                }
 100                putchar('\n');
 101        }
 102}
 103
 104static void list_commands(const char *exec_path, const char *pattern)
 105{
 106        unsigned int longest = 0;
 107        char path[PATH_MAX];
 108        int dirlen;
 109        DIR *dir = opendir(exec_path);
 110        struct dirent *de;
 111
 112        if (!dir) {
 113                fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
 114                exit(1);
 115        }
 116
 117        dirlen = strlen(exec_path);
 118        if (PATH_MAX - 20 < dirlen) {
 119                fprintf(stderr, "git: insanely long exec-path '%s'\n",
 120                        exec_path);
 121                exit(1);
 122        }
 123
 124        memcpy(path, exec_path, dirlen);
 125        path[dirlen++] = '/';
 126
 127        while ((de = readdir(dir)) != NULL) {
 128                struct stat st;
 129                int entlen;
 130
 131                if (strncmp(de->d_name, "git-", 4))
 132                        continue;
 133                strcpy(path+dirlen, de->d_name);
 134                if (stat(path, &st) || /* stat, not lstat */
 135                    !S_ISREG(st.st_mode) ||
 136                    !(st.st_mode & S_IXUSR))
 137                        continue;
 138
 139                entlen = strlen(de->d_name);
 140                if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
 141                        entlen -= 4;
 142
 143                if (longest < entlen)
 144                        longest = entlen;
 145
 146                add_cmdname(de->d_name + 4, entlen-4);
 147        }
 148        closedir(dir);
 149
 150        printf("git commands available in '%s'\n", exec_path);
 151        printf("----------------------------");
 152        mput_char('-', strlen(exec_path));
 153        putchar('\n');
 154        pretty_print_string_list(cmdname, longest - 4);
 155        putchar('\n');
 156}
 157
 158#ifdef __GNUC__
 159static void usage(const char *exec_path, const char *fmt, ...)
 160        __attribute__((__format__(__printf__, 2, 3), __noreturn__));
 161#endif
 162static void usage(const char *exec_path, const char *fmt, ...)
 163{
 164        if (fmt) {
 165                va_list ap;
 166
 167                va_start(ap, fmt);
 168                printf("git: ");
 169                vprintf(fmt, ap);
 170                va_end(ap);
 171                putchar('\n');
 172        }
 173        else
 174                puts(git_usage);
 175
 176        putchar('\n');
 177
 178        if(exec_path)
 179                list_commands(exec_path, "git-*");
 180
 181        exit(1);
 182}
 183
 184static void prepend_to_path(const char *dir, int len)
 185{
 186        char *path, *old_path = getenv("PATH");
 187        int path_len = len;
 188
 189        if (!old_path)
 190                old_path = "/usr/local/bin:/usr/bin:/bin";
 191
 192        path_len = len + strlen(old_path) + 1;
 193
 194        path = malloc(path_len + 1);
 195        path[path_len + 1] = '\0';
 196
 197        memcpy(path, dir, len);
 198        path[len] = ':';
 199        memcpy(path + len + 1, old_path, path_len - len);
 200
 201        setenv("PATH", path, 1);
 202}
 203
 204static void show_man_page(char *git_cmd)
 205{
 206        char *page;
 207
 208        if (!strncmp(git_cmd, "git", 3))
 209                page = git_cmd;
 210        else {
 211                int page_len = strlen(git_cmd) + 4;
 212
 213                page = malloc(page_len + 1);
 214                strcpy(page, "git-");
 215                strcpy(page + 4, git_cmd);
 216                page[page_len] = 0;
 217        }
 218
 219        execlp("man", "man", page, NULL);
 220}
 221
 222int main(int argc, char **argv, char **envp)
 223{
 224        char git_command[PATH_MAX + 1];
 225        char wd[PATH_MAX + 1];
 226        int i, len, show_help = 0;
 227        char *exec_path = getenv("GIT_EXEC_PATH");
 228
 229        getcwd(wd, PATH_MAX);
 230
 231        if (!exec_path)
 232                exec_path = GIT_EXEC_PATH;
 233
 234        for (i = 1; i < argc; i++) {
 235                char *arg = argv[i];
 236
 237                if (strncmp(arg, "--", 2))
 238                        break;
 239
 240                arg += 2;
 241
 242                if (!strncmp(arg, "exec-path", 9)) {
 243                        arg += 9;
 244                        if (*arg == '=')
 245                                exec_path = arg + 1;
 246                        else {
 247                                puts(exec_path);
 248                                exit(0);
 249                        }
 250                }
 251                else if (!strcmp(arg, "version")) {
 252                        printf("git version %s\n", GIT_VERSION);
 253                        exit(0);
 254                }
 255                else if (!strcmp(arg, "help"))
 256                        show_help = 1;
 257                else if (!show_help)
 258                        usage(NULL, NULL);
 259        }
 260
 261        if (i >= argc || show_help) {
 262                if (i >= argc)
 263                        usage(exec_path, NULL);
 264
 265                show_man_page(argv[i]);
 266        }
 267
 268        if (*exec_path != '/') {
 269                if (!getcwd(git_command, sizeof(git_command))) {
 270                        fprintf(stderr,
 271                                "git: cannot determine current directory");
 272                        exit(1);
 273                }
 274                len = strlen(git_command);
 275
 276                /* Trivial cleanup */
 277                while (!strncmp(exec_path, "./", 2)) {
 278                        exec_path += 2;
 279                        while (*exec_path == '/')
 280                                exec_path++;
 281                }
 282                snprintf(git_command + len, sizeof(git_command) - len,
 283                         "/%s", exec_path);
 284        }
 285        else
 286                strcpy(git_command, exec_path);
 287        len = strlen(git_command);
 288        prepend_to_path(git_command, len);
 289
 290        len += snprintf(git_command + len, sizeof(git_command) - len,
 291                        "/git-%s", argv[i]);
 292        if (sizeof(git_command) <= len) {
 293                fprintf(stderr, "git: command name given is too long (%d)\n", len);
 294                exit(1);
 295        }
 296
 297        /* execve() can only ever return if it fails */
 298        execve(git_command, &argv[i], envp);
 299
 300        if (errno == ENOENT)
 301                usage(exec_path, "'%s' is not a git-command", argv[i]);
 302
 303        fprintf(stderr, "Failed to run command '%s': %s\n",
 304                git_command, strerror(errno));
 305
 306        return 1;
 307}