c0bd19d0ef2f44608425e35b6e80ff1baef31564
   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 "git-compat-util.h"
  12#include "exec_cmd.h"
  13#include "cache.h"
  14#include "quote.h"
  15
  16#include "builtin.h"
  17
  18static void prepend_to_path(const char *dir, int len)
  19{
  20        const char *old_path = getenv("PATH");
  21        char *path;
  22        int path_len = len;
  23
  24        if (!old_path)
  25                old_path = "/usr/local/bin:/usr/bin:/bin";
  26
  27        path_len = len + strlen(old_path) + 1;
  28
  29        path = malloc(path_len + 1);
  30
  31        memcpy(path, dir, len);
  32        path[len] = ':';
  33        memcpy(path + len + 1, old_path, path_len - len);
  34
  35        setenv("PATH", path, 1);
  36}
  37
  38static int handle_options(const char*** argv, int* argc)
  39{
  40        int handled = 0;
  41
  42        while (*argc > 0) {
  43                const char *cmd = (*argv)[0];
  44                if (cmd[0] != '-')
  45                        break;
  46
  47                if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
  48                        setup_pager();
  49                } else
  50                        die ("Unknown option: %s", cmd);
  51
  52                (*argv)++;
  53                (*argc)--;
  54                handled++;
  55        }
  56        return handled;
  57}
  58
  59static const char *alias_command;
  60static char *alias_string = NULL;
  61
  62static int git_alias_config(const char *var, const char *value)
  63{
  64        if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) {
  65                alias_string = strdup(value);
  66        }
  67        return 0;
  68}
  69
  70static int split_cmdline(char *cmdline, const char ***argv)
  71{
  72        int src, dst, count = 0, size = 16;
  73        char quoted = 0;
  74
  75        *argv = malloc(sizeof(char*) * size);
  76
  77        /* split alias_string */
  78        (*argv)[count++] = cmdline;
  79        for (src = dst = 0; cmdline[src];) {
  80                char c = cmdline[src];
  81                if (!quoted && isspace(c)) {
  82                        cmdline[dst++] = 0;
  83                        while (cmdline[++src]
  84                                        && isspace(cmdline[src]))
  85                                ; /* skip */
  86                        if (count >= size) {
  87                                size += 16;
  88                                *argv = realloc(*argv, sizeof(char*) * size);
  89                        }
  90                        (*argv)[count++] = cmdline + dst;
  91                } else if(!quoted && (c == '\'' || c == '"')) {
  92                        quoted = c;
  93                        src++;
  94                } else if (c == quoted) {
  95                        quoted = 0;
  96                        src++;
  97                } else {
  98                        if (c == '\\' && quoted != '\'') {
  99                                src++;
 100                                c = cmdline[src];
 101                                if (!c) {
 102                                        free(*argv);
 103                                        *argv = NULL;
 104                                        return error("cmdline ends with \\");
 105                                }
 106                        }
 107                        cmdline[dst++] = c;
 108                        src++;
 109                }
 110        }
 111
 112        cmdline[dst] = 0;
 113
 114        if (quoted) {
 115                free(*argv);
 116                *argv = NULL;
 117                return error("unclosed quote");
 118        }
 119
 120        return count;
 121}
 122
 123static int handle_alias(int *argcp, const char ***argv)
 124{
 125        int nongit = 0, ret = 0, saved_errno = errno;
 126        const char *subdir;
 127
 128        subdir = setup_git_directory_gently(&nongit);
 129        if (!nongit) {
 130                int count, option_count;
 131                const char** new_argv;
 132
 133                alias_command = (*argv)[0];
 134                git_config(git_alias_config);
 135                if (alias_string) {
 136
 137                        count = split_cmdline(alias_string, &new_argv);
 138                        option_count = handle_options(&new_argv, &count);
 139                        memmove(new_argv - option_count, new_argv,
 140                                        count * sizeof(char *));
 141                        new_argv -= option_count;
 142
 143                        if (count < 1)
 144                                die("empty alias for %s", alias_command);
 145
 146                        if (!strcmp(alias_command, new_argv[0]))
 147                                die("recursive alias: %s", alias_command);
 148
 149                        if (getenv("GIT_TRACE")) {
 150                                int i;
 151                                fprintf(stderr, "trace: alias expansion: %s =>",
 152                                        alias_command);
 153                                for (i = 0; i < count; ++i) {
 154                                        fputc(' ', stderr);
 155                                        sq_quote_print(stderr, new_argv[i]);
 156                                }
 157                                fputc('\n', stderr);
 158                                fflush(stderr);
 159                        }
 160
 161                        new_argv = realloc(new_argv, sizeof(char*) *
 162                                           (count + *argcp + 1));
 163                        /* insert after command name */
 164                        memcpy(new_argv + count, *argv + 1,
 165                               sizeof(char*) * *argcp);
 166                        new_argv[count+*argcp] = NULL;
 167
 168                        *argv = new_argv;
 169                        *argcp += count - 1;
 170
 171                        ret = 1;
 172                }
 173        }
 174
 175        if (subdir)
 176                chdir(subdir);
 177
 178        errno = saved_errno;
 179
 180        return ret;
 181}
 182
 183const char git_version_string[] = GIT_VERSION;
 184
 185static void handle_internal_command(int argc, const char **argv, char **envp)
 186{
 187        const char *cmd = argv[0];
 188        static struct cmd_struct {
 189                const char *cmd;
 190                int (*fn)(int, const char **, char **);
 191        } commands[] = {
 192                { "version", cmd_version },
 193                { "help", cmd_help },
 194                { "log", cmd_log },
 195                { "whatchanged", cmd_whatchanged },
 196                { "show", cmd_show },
 197                { "push", cmd_push },
 198                { "format-patch", cmd_format_patch },
 199                { "count-objects", cmd_count_objects },
 200                { "diff", cmd_diff },
 201                { "grep", cmd_grep },
 202                { "rm", cmd_rm },
 203                { "add", cmd_add },
 204                { "rev-list", cmd_rev_list },
 205                { "init-db", cmd_init_db },
 206                { "get-tar-commit-id", cmd_get_tar_commit_id },
 207                { "upload-tar", cmd_upload_tar },
 208                { "check-ref-format", cmd_check_ref_format },
 209                { "ls-files", cmd_ls_files },
 210                { "ls-tree", cmd_ls_tree },
 211                { "tar-tree", cmd_tar_tree },
 212                { "read-tree", cmd_read_tree },
 213                { "commit-tree", cmd_commit_tree },
 214                { "apply", cmd_apply },
 215                { "show-branch", cmd_show_branch },
 216                { "diff-files", cmd_diff_files },
 217                { "diff-index", cmd_diff_index },
 218                { "diff-stages", cmd_diff_stages },
 219                { "diff-tree", cmd_diff_tree },
 220                { "cat-file", cmd_cat_file },
 221                { "rev-parse", cmd_rev_parse },
 222                { "write-tree", cmd_write_tree },
 223                { "mailsplit", cmd_mailsplit },
 224                { "mailinfo", cmd_mailinfo },
 225                { "stripspace", cmd_stripspace },
 226                { "update-index", cmd_update_index },
 227                { "update-ref", cmd_update_ref },
 228                { "fmt-merge-msg", cmd_fmt_merge_msg },
 229                { "prune", cmd_prune },
 230        };
 231        int i;
 232
 233        /* Turn "git cmd --help" into "git help cmd" */
 234        if (argc > 1 && !strcmp(argv[1], "--help")) {
 235                argv[1] = argv[0];
 236                argv[0] = cmd = "help";
 237        }
 238
 239        for (i = 0; i < ARRAY_SIZE(commands); i++) {
 240                struct cmd_struct *p = commands+i;
 241                if (strcmp(p->cmd, cmd))
 242                        continue;
 243
 244                if (getenv("GIT_TRACE")) {
 245                        int i;
 246                        fprintf(stderr, "trace: built-in: git");
 247                        for (i = 0; i < argc; ++i) {
 248                                fputc(' ', stderr);
 249                                sq_quote_print(stderr, argv[i]);
 250                        }
 251                        putc('\n', stderr);
 252                        fflush(stderr);
 253                }
 254
 255                exit(p->fn(argc, argv, envp));
 256        }
 257}
 258
 259int main(int argc, const char **argv, char **envp)
 260{
 261        const char *cmd = argv[0];
 262        char *slash = strrchr(cmd, '/');
 263        const char *exec_path = NULL;
 264        int done_alias = 0;
 265
 266        /*
 267         * Take the basename of argv[0] as the command
 268         * name, and the dirname as the default exec_path
 269         * if it's an absolute path and we don't have
 270         * anything better.
 271         */
 272        if (slash) {
 273                *slash++ = 0;
 274                if (*cmd == '/')
 275                        exec_path = cmd;
 276                cmd = slash;
 277        }
 278
 279        /*
 280         * "git-xxxx" is the same as "git xxxx", but we obviously:
 281         *
 282         *  - cannot take flags in between the "git" and the "xxxx".
 283         *  - cannot execute it externally (since it would just do
 284         *    the same thing over again)
 285         *
 286         * So we just directly call the internal command handler, and
 287         * die if that one cannot handle it.
 288         */
 289        if (!strncmp(cmd, "git-", 4)) {
 290                cmd += 4;
 291                argv[0] = cmd;
 292                handle_internal_command(argc, argv, envp);
 293                die("cannot handle %s internally", cmd);
 294        }
 295
 296        /* Default command: "help" */
 297        cmd = "help";
 298
 299        /* Look for flags.. */
 300        while (argc > 1) {
 301                argv++;
 302                argc--;
 303
 304                handle_options(&argv, &argc);
 305
 306                cmd = *argv;
 307
 308                if (strncmp(cmd, "--", 2))
 309                        break;
 310
 311                cmd += 2;
 312
 313                /*
 314                 * For legacy reasons, the "version" and "help"
 315                 * commands can be written with "--" prepended
 316                 * to make them look like flags.
 317                 */
 318                if (!strcmp(cmd, "help"))
 319                        break;
 320                if (!strcmp(cmd, "version"))
 321                        break;
 322
 323                /*
 324                 * Check remaining flags (which by now must be
 325                 * "--exec-path", but maybe we will accept
 326                 * other arguments some day)
 327                 */
 328                if (!strncmp(cmd, "exec-path", 9)) {
 329                        cmd += 9;
 330                        if (*cmd == '=') {
 331                                git_set_exec_path(cmd + 1);
 332                                continue;
 333                        }
 334                        puts(git_exec_path());
 335                        exit(0);
 336                }
 337                cmd_usage(0, NULL, NULL);
 338        }
 339        argv[0] = cmd;
 340
 341        /*
 342         * We search for git commands in the following order:
 343         *  - git_exec_path()
 344         *  - the path of the "git" command if we could find it
 345         *    in $0
 346         *  - the regular PATH.
 347         */
 348        if (exec_path)
 349                prepend_to_path(exec_path, strlen(exec_path));
 350        exec_path = git_exec_path();
 351        prepend_to_path(exec_path, strlen(exec_path));
 352
 353        while (1) {
 354                /* See if it's an internal command */
 355                handle_internal_command(argc, argv, envp);
 356
 357                /* .. then try the external ones */
 358                execv_git_cmd(argv);
 359
 360                /* It could be an alias -- this works around the insanity
 361                 * of overriding "git log" with "git show" by having
 362                 * alias.log = show
 363                 */
 364                if (done_alias || !handle_alias(&argc, &argv))
 365                        break;
 366                done_alias = 1;
 367        }
 368
 369        if (errno == ENOENT)
 370                cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);
 371
 372        fprintf(stderr, "Failed to run command '%s': %s\n",
 373                cmd, strerror(errno));
 374
 375        return 1;
 376}