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"
1718
static void prepend_to_path(const char *dir, int len)
19{
20const char *old_path = getenv("PATH");
21char *path;
22int path_len = len;
2324
if (!old_path)
25old_path = "/usr/local/bin:/usr/bin:/bin";
2627
path_len = len + strlen(old_path) + 1;
2829
path = malloc(path_len + 1);
3031
memcpy(path, dir, len);
32path[len] = ':';
33memcpy(path + len + 1, old_path, path_len - len);
3435
setenv("PATH", path, 1);
36}
3738
static int handle_options(const char*** argv, int* argc)
39{
40int handled = 0;
4142
while (*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 "--" prepended
50* 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}
9091
static const char *alias_command;
92static char *alias_string = NULL;
9394
static 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}
101102
static 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}
143144
cmdline[dst] = 0;
145146
if (quoted) {
147free(*argv);
148*argv = NULL;
149return error("unclosed quote");
150}
151152
return count;
153}
154155
static 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;
161162
subdir = setup_git_directory_gently(&nongit);
163164
alias_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;
172173
if (count < 1)
174die("empty alias for %s", alias_command);
175176
if (!strcmp(alias_command, new_argv[0]))
177die("recursive alias: %s", alias_command);
178179
if (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}
190191
new_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;
199200
ret = 1;
201}
202203
if (subdir)
204chdir(subdir);
205206
errno = saved_errno;
207208
return ret;
209}
210211
const char git_version_string[] = GIT_VERSION;
212213
#define NEEDS_PREFIX 1
214#define USE_PAGER 2
215216
static 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}
271272
for (i = 0; i < ARRAY_SIZE(commands); i++) {
273struct cmd_struct *p = commands+i;
274const char *prefix;
275if (strcmp(p->cmd, cmd))
276continue;
277278
prefix = 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}
293294
exit(p->fn(argc, argv, prefix));
295}
296}
297298
int 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 command
307* name, and the dirname as the default exec_path
308* if it's an absolute path and we don't have
309* 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 do
323* the same thing over again)
324*
325* So we just directly call the internal command handler, and
326* 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 it
353* in $0
354* - 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));
360361
while (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 insanity
369* of overriding "git log" with "git show" by having
370* alias.log = show
371*/
372if (done_alias || !handle_alias(&argc, &argv))
373break;
374done_alias = 1;
375}
376377
if (errno == ENOENT)
378cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);
379380
fprintf(stderr, "Failed to run command '%s': %s\n",
381cmd, strerror(errno));
382383
return 1;
384}