X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=git.c;h=329ebec78cd057a14a22cca2e89a9cb09a53f5dd;hb=2f57c69792146e6c178a05edec18ab15d0138ad6;hp=e795ddb8106138920ad02793c4f4b787e05fb4f4;hpb=7246ed438c541650c2cd50e4e98b43226f60da0c;p=git.git diff --git a/git.c b/git.c index e795ddb8..329ebec7 100644 --- a/git.c +++ b/git.c @@ -8,307 +8,303 @@ #include #include #include -#include #include "git-compat-util.h" +#include "exec_cmd.h" +#include "cache.h" -#ifndef PATH_MAX -# define PATH_MAX 4096 -#endif +#include "builtin.h" -static const char git_usage[] = - "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; - -/* most gui terms set COLUMNS (although some don't export it) */ -static int term_columns(void) +static void prepend_to_path(const char *dir, int len) { - char *col_string = getenv("COLUMNS"); - int n_cols = 0; - - if (col_string && (n_cols = atoi(col_string)) > 0) - return n_cols; - -#ifdef TIOCGWINSZ - { - struct winsize ws; - if (!ioctl(1, TIOCGWINSZ, &ws)) { - if (ws.ws_col) - return ws.ws_col; - } - } -#endif + char *path, *old_path = getenv("PATH"); + int path_len = len; - return 80; -} + if (!old_path) + old_path = "/usr/local/bin:/usr/bin:/bin"; -static void oom(void) -{ - fprintf(stderr, "git: out of memory\n"); - exit(1); -} + path_len = len + strlen(old_path) + 1; -static inline void mput_char(char c, unsigned int num) -{ - while(num--) - putchar(c); -} + path = malloc(path_len + 1); -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; + memcpy(path, dir, len); + path[len] = ':'; + memcpy(path + len + 1, old_path, path_len - len); -static void add_cmdname(const char *name, int len) -{ - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - if (!cmdname) - oom(); - } - ent = malloc(sizeof(*ent) + len); - if (!ent) - oom(); - ent->len = len; - memcpy(ent->name, name, len); - ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; + setenv("PATH", path, 1); } -static int cmdname_compare(const void *a_, const void *b_) -{ - struct cmdname *a = *(struct cmdname **)a_; - struct cmdname *b = *(struct cmdname **)b_; - return strcmp(a->name, b->name); -} +static const char *alias_command; +static char *alias_string = NULL; -static void pretty_print_string_list(struct cmdname **cmdname, int longest) +static int git_alias_config(const char *var, const char *value) { - int cols = 1, rows; - int space = longest + 1; /* min 1 SP between words */ - int max_cols = term_columns() - 1; /* don't print *on* the edge */ - int i, j; - - if (space < max_cols) - cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); - - for (i = 0; i < rows; i++) { - printf(" "); - - for (j = 0; j < cols; j++) { - int n = j * rows + i; - int size = space; - if (n >= cmdname_cnt) - break; - if (j == cols-1 || n + rows >= cmdname_cnt) - size = 1; - printf("%-*s", size, cmdname[n]->name); - } - putchar('\n'); + if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { + alias_string = strdup(value); } + return 0; } -static void list_commands(const char *exec_path, const char *pattern) +static int split_cmdline(char *cmdline, const char ***argv) { - unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); - struct dirent *de; - - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); + int src, dst, count = 0, size = 16; + char quoted = 0; + + *argv = malloc(sizeof(char*) * size); + + /* split alias_string */ + (*argv)[count++] = cmdline; + for (src = dst = 0; cmdline[src];) { + char c = cmdline[src]; + if (!quoted && isspace(c)) { + cmdline[dst++] = 0; + while (cmdline[++src] + && isspace(cmdline[src])) + ; /* skip */ + if (count >= size) { + size += 16; + *argv = realloc(*argv, sizeof(char*) * size); + } + (*argv)[count++] = cmdline + dst; + } else if(!quoted && (c == '\'' || c == '"')) { + quoted = c; + src++; + } else if (c == quoted) { + quoted = 0; + src++; + } else { + if (c == '\\' && quoted != '\'') { + src++; + c = cmdline[src]; + if (!c) { + free(*argv); + *argv = NULL; + return error("cmdline ends with \\"); + } + } + cmdline[dst++] = c; + src++; + } } - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; + cmdline[dst] = 0; - while ((de = readdir(dir)) != NULL) { - struct stat st; - int entlen; - - if (strncmp(de->d_name, "git-", 4)) - continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ - !S_ISREG(st.st_mode) || - !(st.st_mode & S_IXUSR)) - continue; - - entlen = strlen(de->d_name); - if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) - entlen -= 4; - - if (longest < entlen) - longest = entlen; - - add_cmdname(de->d_name + 4, entlen-4); + if (quoted) { + free(*argv); + *argv = NULL; + return error("unclosed quote"); } - closedir(dir); - - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); + + return count; } -#ifdef __GNUC__ -static void cmd_usage(const char *exec_path, const char *fmt, ...) - __attribute__((__format__(__printf__, 2, 3), __noreturn__)); -#endif -static void cmd_usage(const char *exec_path, const char *fmt, ...) +static int handle_alias(int *argcp, const char ***argv) { - if (fmt) { - va_list ap; - - va_start(ap, fmt); - printf("git: "); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); - } - else - puts(git_usage); + int nongit = 0, ret = 0; + const char *subdir; - putchar('\n'); + subdir = setup_git_directory_gently(&nongit); + if (!nongit) { + int count; + const char** new_argv; - if(exec_path) - list_commands(exec_path, "git-*"); + alias_command = (*argv)[0]; + git_config(git_alias_config); + if (alias_string) { - exit(1); -} + count = split_cmdline(alias_string, &new_argv); -static void prepend_to_path(const char *dir, int len) -{ - char *path, *old_path = getenv("PATH"); - int path_len = len; + if (count < 1) + die("empty alias for %s", alias_command); - if (!old_path) - old_path = "/usr/local/bin:/usr/bin:/bin"; + if (!strcmp(alias_command, new_argv[0])) + die("recursive alias: %s", alias_command); - path_len = len + strlen(old_path) + 1; + /* insert after command name */ + if (*argcp > 1) { + new_argv = realloc(new_argv, sizeof(char*) * + (count + *argcp)); + memcpy(new_argv + count, *argv + 1, + sizeof(char*) * *argcp); + } - path = malloc(path_len + 1); + *argv = new_argv; + *argcp += count - 1; - memcpy(path, dir, len); - path[len] = ':'; - memcpy(path + len + 1, old_path, path_len - len); + ret = 1; + } + } - setenv("PATH", path, 1); -} + if (subdir) + chdir(subdir); -static void show_man_page(char *git_cmd) -{ - char *page; + return ret; +} - if (!strncmp(git_cmd, "git", 3)) - page = git_cmd; - else { - int page_len = strlen(git_cmd) + 4; +const char git_version_string[] = GIT_VERSION; - page = malloc(page_len + 1); - strcpy(page, "git-"); - strcpy(page + 4, git_cmd); - page[page_len] = 0; +static void handle_internal_command(int argc, const char **argv, char **envp) +{ + const char *cmd = argv[0]; + static struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, char **); + } commands[] = { + { "version", cmd_version }, + { "help", cmd_help }, + { "log", cmd_log }, + { "whatchanged", cmd_whatchanged }, + { "show", cmd_show }, + { "push", cmd_push }, + { "format-patch", cmd_format_patch }, + { "count-objects", cmd_count_objects }, + { "diff", cmd_diff }, + { "grep", cmd_grep }, + { "rm", cmd_rm }, + { "add", cmd_add }, + { "rev-list", cmd_rev_list }, + { "init-db", cmd_init_db }, + { "get-tar-commit-id", cmd_get_tar_commit_id }, + { "upload-tar", cmd_upload_tar }, + { "check-ref-format", cmd_check_ref_format }, + { "ls-files", cmd_ls_files }, + { "ls-tree", cmd_ls_tree }, + { "tar-tree", cmd_tar_tree }, + { "read-tree", cmd_read_tree }, + { "commit-tree", cmd_commit_tree }, + { "apply", cmd_apply }, + { "show-branch", cmd_show_branch }, + { "diff-files", cmd_diff_files }, + { "diff-index", cmd_diff_index }, + { "diff-stages", cmd_diff_stages }, + { "diff-tree", cmd_diff_tree }, + { "cat-file", cmd_cat_file }, + { "rev-parse", cmd_rev_parse } + }; + int i; + + /* Turn "git cmd --help" into "git help cmd" */ + if (argc > 1 && !strcmp(argv[1], "--help")) { + argv[1] = argv[0]; + argv[0] = cmd = "help"; } - execlp("man", "man", page, NULL); + for (i = 0; i < ARRAY_SIZE(commands); i++) { + struct cmd_struct *p = commands+i; + if (strcmp(p->cmd, cmd)) + continue; + exit(p->fn(argc, argv, envp)); + } } -int main(int argc, char **argv, char **envp) +int main(int argc, const char **argv, char **envp) { + const char *cmd = argv[0]; + char *slash = strrchr(cmd, '/'); char git_command[PATH_MAX + 1]; - char wd[PATH_MAX + 1]; - int i, len, show_help = 0; - char *exec_path = getenv("GIT_EXEC_PATH"); + const char *exec_path = NULL; + int done_alias = 0; + + /* + * Take the basename of argv[0] as the command + * name, and the dirname as the default exec_path + * if it's an absolute path and we don't have + * anything better. + */ + if (slash) { + *slash++ = 0; + if (*cmd == '/') + exec_path = cmd; + cmd = slash; + } - getcwd(wd, PATH_MAX); + /* + * "git-xxxx" is the same as "git xxxx", but we obviously: + * + * - cannot take flags in between the "git" and the "xxxx". + * - cannot execute it externally (since it would just do + * the same thing over again) + * + * So we just directly call the internal command handler, and + * die if that one cannot handle it. + */ + if (!strncmp(cmd, "git-", 4)) { + cmd += 4; + argv[0] = cmd; + handle_internal_command(argc, argv, envp); + die("cannot handle %s internally", cmd); + } - if (!exec_path) - exec_path = GIT_EXEC_PATH; + /* Default command: "help" */ + cmd = "help"; - for (i = 1; i < argc; i++) { - char *arg = argv[i]; + /* Look for flags.. */ + while (argc > 1) { + cmd = *++argv; + argc--; - if (strncmp(arg, "--", 2)) + if (strncmp(cmd, "--", 2)) break; - arg += 2; + cmd += 2; - if (!strncmp(arg, "exec-path", 9)) { - arg += 9; - if (*arg == '=') - exec_path = arg + 1; - else { - puts(exec_path); - exit(0); + /* + * For legacy reasons, the "version" and "help" + * commands can be written with "--" prepended + * to make them look like flags. + */ + if (!strcmp(cmd, "help")) + break; + if (!strcmp(cmd, "version")) + break; + + /* + * Check remaining flags (which by now must be + * "--exec-path", but maybe we will accept + * other arguments some day) + */ + if (!strncmp(cmd, "exec-path", 9)) { + cmd += 9; + if (*cmd == '=') { + git_set_exec_path(cmd + 1); + continue; } - } - else if (!strcmp(arg, "version")) { - printf("git version %s\n", GIT_VERSION); + puts(git_exec_path()); exit(0); } - else if (!strcmp(arg, "help")) - show_help = 1; - else if (!show_help) - cmd_usage(NULL, NULL); - } - - if (i >= argc || show_help) { - if (i >= argc) - cmd_usage(exec_path, NULL); - - show_man_page(argv[i]); - } - - if (*exec_path != '/') { - if (!getcwd(git_command, sizeof(git_command))) { - fprintf(stderr, - "git: cannot determine current directory\n"); - exit(1); - } - len = strlen(git_command); - - /* Trivial cleanup */ - while (!strncmp(exec_path, "./", 2)) { - exec_path += 2; - while (*exec_path == '/') - exec_path++; - } - snprintf(git_command + len, sizeof(git_command) - len, - "/%s", exec_path); + cmd_usage(0, NULL, NULL); } - else - strcpy(git_command, exec_path); - len = strlen(git_command); - prepend_to_path(git_command, len); - - len += snprintf(git_command + len, sizeof(git_command) - len, - "/git-%s", argv[i]); - if (sizeof(git_command) <= len) { - fprintf(stderr, "git: command name given is too long.\n"); - exit(1); + argv[0] = cmd; + + /* + * We search for git commands in the following order: + * - git_exec_path() + * - the path of the "git" command if we could find it + * in $0 + * - the regular PATH. + */ + if (exec_path) + prepend_to_path(exec_path, strlen(exec_path)); + exec_path = git_exec_path(); + prepend_to_path(exec_path, strlen(exec_path)); + + while (1) { + /* See if it's an internal command */ + handle_internal_command(argc, argv, envp); + + /* .. then try the external ones */ + execv_git_cmd(argv); + + /* It could be an alias -- this works around the insanity + * of overriding "git log" with "git show" by having + * alias.log = show + */ + if (done_alias || !handle_alias(&argc, &argv)) + break; + done_alias = 1; } - /* execve() can only ever return if it fails */ - execve(git_command, &argv[i], envp); - if (errno == ENOENT) - cmd_usage(exec_path, "'%s' is not a git-command", argv[i]); + cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); fprintf(stderr, "Failed to run command '%s': %s\n", git_command, strerror(errno));