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"1516#include "builtin.h"1718static void prepend_to_path(const char *dir, int len)19{20const char *old_path = getenv("PATH");21char *path;22int path_len = len;2324if (!old_path)25old_path = "/usr/local/bin:/usr/bin:/bin";2627path_len = len + strlen(old_path) + 1;2829path = malloc(path_len + 1);3031memcpy(path, dir, len);32path[len] = ':';33memcpy(path + len + 1, old_path, path_len - len);3435setenv("PATH", path, 1);36}3738static int handle_options(const char*** argv, int* argc)39{40int handled = 0;4142while (*argc > 0) {43const char *cmd = (*argv)[0];44if (cmd[0] != '-')45break;4647/*48* For legacy reasons, the "version" and "help"49* commands can be written with "--" prepended50* to make them look like flags.51*/52if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))53break;5455/*56* Check remaining flags.57*/58if (!strncmp(cmd, "--exec-path", 11)) {59cmd += 11;60if (*cmd == '=')61git_set_exec_path(cmd + 1);62else {63puts(git_exec_path());64exit(0);65}66} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {67setup_pager();68} else if (!strcmp(cmd, "--git-dir")) {69if (*argc < 1)70return -1;71setenv("GIT_DIR", (*argv)[1], 1);72(*argv)++;73(*argc)--;74} else if (!strncmp(cmd, "--git-dir=", 10)) {75setenv("GIT_DIR", cmd + 10, 1);76} else if (!strcmp(cmd, "--bare")) {77static char git_dir[1024];78setenv("GIT_DIR", getcwd(git_dir, 1024), 1);79} else {80fprintf(stderr, "Unknown option: %s\n", cmd);81cmd_usage(0, NULL, NULL);82}8384(*argv)++;85(*argc)--;86handled++;87}88return handled;89}9091static const char *alias_command;92static char *alias_string = NULL;9394static int git_alias_config(const char *var, const char *value)95{96if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) {97alias_string = strdup(value);98}99return 0;100}101102static int split_cmdline(char *cmdline, const char ***argv)103{104int src, dst, count = 0, size = 16;105char quoted = 0;106107*argv = malloc(sizeof(char*) * size);108109/* split alias_string */110(*argv)[count++] = cmdline;111for (src = dst = 0; cmdline[src];) {112char c = cmdline[src];113if (!quoted && isspace(c)) {114cmdline[dst++] = 0;115while (cmdline[++src]116&& isspace(cmdline[src]))117; /* skip */118if (count >= size) {119size += 16;120*argv = realloc(*argv, sizeof(char*) * size);121}122(*argv)[count++] = cmdline + dst;123} else if(!quoted && (c == '\'' || c == '"')) {124quoted = c;125src++;126} else if (c == quoted) {127quoted = 0;128src++;129} else {130if (c == '\\' && quoted != '\'') {131src++;132c = cmdline[src];133if (!c) {134free(*argv);135*argv = NULL;136return error("cmdline ends with \\");137}138}139cmdline[dst++] = c;140src++;141}142}143144cmdline[dst] = 0;145146if (quoted) {147free(*argv);148*argv = NULL;149return error("unclosed quote");150}151152return count;153}154155static int handle_alias(int *argcp, const char ***argv)156{157int nongit = 0, ret = 0, saved_errno = errno;158const char *subdir;159int count, option_count;160const char** new_argv;161162subdir = setup_git_directory_gently(&nongit);163164alias_command = (*argv)[0];165git_config(git_alias_config);166if (alias_string) {167count = split_cmdline(alias_string, &new_argv);168option_count = handle_options(&new_argv, &count);169memmove(new_argv - option_count, new_argv,170count * sizeof(char *));171new_argv -= option_count;172173if (count < 1)174die("empty alias for %s", alias_command);175176if (!strcmp(alias_command, new_argv[0]))177die("recursive alias: %s", alias_command);178179if (getenv("GIT_TRACE")) {180int i;181fprintf(stderr, "trace: alias expansion: %s =>",182alias_command);183for (i = 0; i < count; ++i) {184fputc(' ', stderr);185sq_quote_print(stderr, new_argv[i]);186}187fputc('\n', stderr);188fflush(stderr);189}190191new_argv = realloc(new_argv, sizeof(char*) *192(count + *argcp + 1));193/* insert after command name */194memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);195new_argv[count+*argcp] = NULL;196197*argv = new_argv;198*argcp += count - 1;199200ret = 1;201}202203if (subdir)204chdir(subdir);205206errno = saved_errno;207208return ret;209}210211const char git_version_string[] = GIT_VERSION;212213#define NEEDS_PREFIX 1214#define USE_PAGER 2215216static void handle_internal_command(int argc, const char **argv, char **envp)217{218const char *cmd = argv[0];219static struct cmd_struct {220const char *cmd;221int (*fn)(int, const char **, const char *);222int option;223} commands[] = {224{ "version", cmd_version },225{ "help", cmd_help },226{ "log", cmd_log, NEEDS_PREFIX | USE_PAGER },227{ "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER },228{ "show", cmd_show, NEEDS_PREFIX | USE_PAGER },229{ "push", cmd_push },230{ "format-patch", cmd_format_patch, NEEDS_PREFIX },231{ "count-objects", cmd_count_objects },232{ "diff", cmd_diff, NEEDS_PREFIX },233{ "grep", cmd_grep, NEEDS_PREFIX },234{ "rm", cmd_rm, NEEDS_PREFIX },235{ "add", cmd_add, NEEDS_PREFIX },236{ "rev-list", cmd_rev_list, NEEDS_PREFIX },237{ "init-db", cmd_init_db },238{ "get-tar-commit-id", cmd_get_tar_commit_id },239{ "upload-tar", cmd_upload_tar },240{ "check-ref-format", cmd_check_ref_format },241{ "ls-files", cmd_ls_files, NEEDS_PREFIX },242{ "ls-tree", cmd_ls_tree, NEEDS_PREFIX },243{ "tar-tree", cmd_tar_tree, NEEDS_PREFIX },244{ "read-tree", cmd_read_tree, NEEDS_PREFIX },245{ "commit-tree", cmd_commit_tree, NEEDS_PREFIX },246{ "apply", cmd_apply },247{ "show-branch", cmd_show_branch, NEEDS_PREFIX },248{ "diff-files", cmd_diff_files, NEEDS_PREFIX },249{ "diff-index", cmd_diff_index, NEEDS_PREFIX },250{ "diff-stages", cmd_diff_stages, NEEDS_PREFIX },251{ "diff-tree", cmd_diff_tree, NEEDS_PREFIX },252{ "cat-file", cmd_cat_file, NEEDS_PREFIX },253{ "rev-parse", cmd_rev_parse, NEEDS_PREFIX },254{ "write-tree", cmd_write_tree, NEEDS_PREFIX },255{ "mailsplit", cmd_mailsplit },256{ "mailinfo", cmd_mailinfo },257{ "stripspace", cmd_stripspace },258{ "update-index", cmd_update_index, NEEDS_PREFIX },259{ "update-ref", cmd_update_ref, NEEDS_PREFIX },260{ "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX },261{ "prune", cmd_prune, NEEDS_PREFIX },262{ "mv", cmd_mv, NEEDS_PREFIX },263};264int i;265266/* Turn "git cmd --help" into "git help cmd" */267if (argc > 1 && !strcmp(argv[1], "--help")) {268argv[1] = argv[0];269argv[0] = cmd = "help";270}271272for (i = 0; i < ARRAY_SIZE(commands); i++) {273struct cmd_struct *p = commands+i;274const char *prefix;275if (strcmp(p->cmd, cmd))276continue;277278prefix = NULL;279if (p->option & NEEDS_PREFIX)280prefix = setup_git_directory();281if (p->option & USE_PAGER)282setup_pager();283if (getenv("GIT_TRACE")) {284int i;285fprintf(stderr, "trace: built-in: git");286for (i = 0; i < argc; ++i) {287fputc(' ', stderr);288sq_quote_print(stderr, argv[i]);289}290putc('\n', stderr);291fflush(stderr);292}293294exit(p->fn(argc, argv, prefix));295}296}297298int main(int argc, const char **argv, char **envp)299{300const char *cmd = argv[0];301char *slash = strrchr(cmd, '/');302const char *exec_path = NULL;303int done_alias = 0;304305/*306* Take the basename of argv[0] as the command307* name, and the dirname as the default exec_path308* if it's an absolute path and we don't have309* anything better.310*/311if (slash) {312*slash++ = 0;313if (*cmd == '/')314exec_path = cmd;315cmd = slash;316}317318/*319* "git-xxxx" is the same as "git xxxx", but we obviously:320*321* - cannot take flags in between the "git" and the "xxxx".322* - cannot execute it externally (since it would just do323* the same thing over again)324*325* So we just directly call the internal command handler, and326* die if that one cannot handle it.327*/328if (!strncmp(cmd, "git-", 4)) {329cmd += 4;330argv[0] = cmd;331handle_internal_command(argc, argv, envp);332die("cannot handle %s internally", cmd);333}334335/* Look for flags.. */336argv++;337argc--;338handle_options(&argv, &argc);339if (argc > 0) {340if (!strncmp(argv[0], "--", 2))341argv[0] += 2;342} else {343/* Default command: "help" */344argv[0] = "help";345argc = 1;346}347cmd = argv[0];348349/*350* We search for git commands in the following order:351* - git_exec_path()352* - the path of the "git" command if we could find it353* in $0354* - the regular PATH.355*/356if (exec_path)357prepend_to_path(exec_path, strlen(exec_path));358exec_path = git_exec_path();359prepend_to_path(exec_path, strlen(exec_path));360361while (1) {362/* See if it's an internal command */363handle_internal_command(argc, argv, envp);364365/* .. then try the external ones */366execv_git_cmd(argv);367368/* It could be an alias -- this works around the insanity369* of overriding "git log" with "git show" by having370* alias.log = show371*/372if (done_alias || !handle_alias(&argc, &argv))373break;374done_alias = 1;375}376377if (errno == ENOENT)378cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);379380fprintf(stderr, "Failed to run command '%s': %s\n",381cmd, strerror(errno));382383return 1;384}