git.c 12.7 KB
Newer Older
1
#include "builtin.h"
2
#include "exec_cmd.h"
3
#include "cache.h"
4
#include "quote.h"
5

6
const char git_usage_string[] =
7
	"git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
8

9 10
static void prepend_to_path(const char *dir, int len)
{
T
Timo Hirvonen 已提交
11 12
	const char *old_path = getenv("PATH");
	char *path;
13 14 15
	int path_len = len;

	if (!old_path)
J
Junio C Hamano 已提交
16
		old_path = "/usr/local/bin:/usr/bin:/bin";
17 18 19

	path_len = len + strlen(old_path) + 1;

J
Jonas Fonseca 已提交
20
	path = xmalloc(path_len + 1);
21 22 23 24 25 26

	memcpy(path, dir, len);
	path[len] = ':';
	memcpy(path + len + 1, old_path, path_len - len);

	setenv("PATH", path, 1);
27 28

	free(path);
29 30
}

31
static int handle_options(const char*** argv, int* argc, int* envchanged)
32 33 34 35 36 37 38 39
{
	int handled = 0;

	while (*argc > 0) {
		const char *cmd = (*argv)[0];
		if (cmd[0] != '-')
			break;

40 41 42 43 44 45 46 47 48 49 50
		/*
		 * For legacy reasons, the "version" and "help"
		 * commands can be written with "--" prepended
		 * to make them look like flags.
		 */
		if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
			break;

		/*
		 * Check remaining flags.
		 */
51
		if (!prefixcmp(cmd, "--exec-path")) {
52 53 54 55 56 57 58 59
			cmd += 11;
			if (*cmd == '=')
				git_set_exec_path(cmd + 1);
			else {
				puts(git_exec_path());
				exit(0);
			}
		} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
60
			setup_pager();
61
		} else if (!strcmp(cmd, "--git-dir")) {
62 63 64 65
			if (*argc < 2) {
				fprintf(stderr, "No directory given for --git-dir.\n" );
				usage(git_usage_string);
			}
66
			setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
67 68
			if (envchanged)
				*envchanged = 1;
69 70
			(*argv)++;
			(*argc)--;
71
			handled++;
72
		} else if (!prefixcmp(cmd, "--git-dir=")) {
73
			setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
74 75
			if (envchanged)
				*envchanged = 1;
76 77 78 79 80 81
		} else if (!strcmp(cmd, "--work-tree")) {
			if (*argc < 2) {
				fprintf(stderr, "No directory given for --work-tree.\n" );
				usage(git_usage_string);
			}
			setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
82 83
			if (envchanged)
				*envchanged = 1;
84 85 86 87
			(*argv)++;
			(*argc)--;
		} else if (!prefixcmp(cmd, "--work-tree=")) {
			setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
88 89
			if (envchanged)
				*envchanged = 1;
90
		} else if (!strcmp(cmd, "--bare")) {
91
			static char git_dir[PATH_MAX+1];
92
			setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
93 94
			if (envchanged)
				*envchanged = 1;
95 96
		} else {
			fprintf(stderr, "Unknown option: %s\n", cmd);
97
			usage(git_usage_string);
98
		}
99 100 101 102 103 104 105 106

		(*argv)++;
		(*argc)--;
		handled++;
	}
	return handled;
}

107
static const char *alias_command;
108
static char *alias_string;
109 110 111

static int git_alias_config(const char *var, const char *value)
{
112
	if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
113
		alias_string = xstrdup(value);
114 115 116 117 118 119 120 121 122
	}
	return 0;
}

static int split_cmdline(char *cmdline, const char ***argv)
{
	int src, dst, count = 0, size = 16;
	char quoted = 0;

J
James Bowes 已提交
123
	*argv = xmalloc(sizeof(char*) * size);
124 125 126 127 128 129 130 131 132 133 134 135

	/* 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;
J
Jonas Fonseca 已提交
136
				*argv = xrealloc(*argv, sizeof(char*) * size);
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
			}
			(*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++;
		}
	}

	cmdline[dst] = 0;

	if (quoted) {
		free(*argv);
		*argv = NULL;
		return error("unclosed quote");
	}

	return count;
}

static int handle_alias(int *argcp, const char ***argv)
{
173
	int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
174
	const char *subdir;
175 176
	int count, option_count;
	const char** new_argv;
177 178

	subdir = setup_git_directory_gently(&nongit);
179 180 181 182

	alias_command = (*argv)[0];
	git_config(git_alias_config);
	if (alias_string) {
183
		if (alias_string[0] == '!') {
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
			if (*argcp > 1) {
				int i, sz = PATH_MAX;
				char *s = xmalloc(sz), *new_alias = s;

				add_to_string(&s, &sz, alias_string, 0);
				free(alias_string);
				alias_string = new_alias;
				for (i = 1; i < *argcp &&
					!add_to_string(&s, &sz, " ", 0) &&
					!add_to_string(&s, &sz, (*argv)[i], 1)
					; i++)
					; /* do nothing */
				if (!sz)
					die("Too many or long arguments");
			}
199 200 201 202 203 204 205 206 207
			trace_printf("trace: alias to shell cmd: %s => %s\n",
				     alias_command, alias_string + 1);
			ret = system(alias_string + 1);
			if (ret >= 0 && WIFEXITED(ret) &&
			    WEXITSTATUS(ret) != 127)
				exit(WEXITSTATUS(ret));
			die("Failed to run '%s' when expanding alias '%s'\n",
			    alias_string + 1, alias_command);
		}
208
		count = split_cmdline(alias_string, &new_argv);
209 210 211 212 213
		option_count = handle_options(&new_argv, &count, &envchanged);
		if (envchanged)
			die("alias '%s' changes environment variables\n"
				 "You can use '!git' in the alias to do this.",
				 alias_command);
214 215 216 217 218 219 220 221 222 223
		memmove(new_argv - option_count, new_argv,
				count * sizeof(char *));
		new_argv -= option_count;

		if (count < 1)
			die("empty alias for %s", alias_command);

		if (!strcmp(alias_command, new_argv[0]))
			die("recursive alias: %s", alias_command);

224 225 226
		trace_argv_printf(new_argv, count,
				  "trace: alias expansion: %s =>",
				  alias_command);
227

J
Jonas Fonseca 已提交
228 229
		new_argv = xrealloc(new_argv, sizeof(char*) *
				    (count + *argcp + 1));
230 231 232
		/* insert after command name */
		memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
		new_argv[count+*argcp] = NULL;
233

234 235
		*argv = new_argv;
		*argcp += count - 1;
236

237
		ret = 1;
238 239 240 241 242
	}

	if (subdir)
		chdir(subdir);

243 244
	errno = saved_errno;

245 246 247
	return ret;
}

248
const char git_version_string[] = GIT_VERSION;
J
Junio C Hamano 已提交
249

250 251
#define RUN_SETUP	(1<<0)
#define USE_PAGER	(1<<1)
252 253 254 255
/*
 * require working tree to be present -- anything uses this needs
 * RUN_SETUP for reading from the configuration file.
 */
256
#define NEED_WORK_TREE	(1<<2)
257

258 259 260 261 262 263 264 265
struct cmd_struct {
	const char *cmd;
	int (*fn)(int, const char **, const char *);
	int option;
};

static int run_command(struct cmd_struct *p, int argc, const char **argv)
{
266 267
	int status;
	struct stat st;
268 269 270 271 272 273 274
	const char *prefix;

	prefix = NULL;
	if (p->option & RUN_SETUP)
		prefix = setup_git_directory();
	if (p->option & USE_PAGER)
		setup_pager();
275 276 277
	if ((p->option & NEED_WORK_TREE) &&
	    (!is_inside_work_tree() || is_inside_git_dir()))
		die("%s must be run in a work tree", p->cmd);
278 279
	trace_argv_printf(argv, argc, "trace: built-in: git");

280 281 282 283 284 285 286 287 288 289 290 291
	status = p->fn(argc, argv, prefix);
	if (status)
		return status;

	/* Somebody closed stdout? */
	if (fstat(fileno(stdout), &st))
		return 0;
	/* Ignore write errors for pipes and sockets.. */
	if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
		return 0;

	/* Check for ENOSPC and EIO errors.. */
292
	if (fflush(stdout))
293
		die("write failure on standard output: %s", strerror(errno));
294 295 296 297
	if (ferror(stdout))
		die("unknown write failure on standard output");
	if (fclose(stdout))
		die("close failed on standard output: %s", strerror(errno));
298
	return 0;
299 300 301
}

static void handle_internal_command(int argc, const char **argv)
302 303
{
	const char *cmd = argv[0];
304
	static struct cmd_struct commands[] = {
305
		{ "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
306
		{ "annotate", cmd_annotate, RUN_SETUP },
J
Junio C Hamano 已提交
307
		{ "apply", cmd_apply },
308
		{ "archive", cmd_archive },
309
		{ "blame", cmd_blame, RUN_SETUP },
J
Junio C Hamano 已提交
310
		{ "branch", cmd_branch, RUN_SETUP },
311
		{ "bundle", cmd_bundle },
312 313
		{ "cat-file", cmd_cat_file, RUN_SETUP },
		{ "checkout-index", cmd_checkout_index, RUN_SETUP },
P
Peter Eriksen 已提交
314
		{ "check-ref-format", cmd_check_ref_format },
315
		{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
R
Rene Scharfe 已提交
316
		{ "cherry", cmd_cherry, RUN_SETUP },
317
		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
318
		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
319
		{ "config", cmd_config },
320
		{ "count-objects", cmd_count_objects, RUN_SETUP },
S
Shawn O. Pearce 已提交
321
		{ "describe", cmd_describe, RUN_SETUP },
322 323
		{ "diff", cmd_diff, USE_PAGER },
		{ "diff-files", cmd_diff_files },
324 325
		{ "diff-index", cmd_diff_index, RUN_SETUP },
		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
326
		{ "fetch--tool", cmd_fetch__tool, RUN_SETUP },
327
		{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
328
		{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
329
		{ "format-patch", cmd_format_patch, RUN_SETUP },
330 331
		{ "fsck", cmd_fsck, RUN_SETUP },
		{ "fsck-objects", cmd_fsck, RUN_SETUP },
J
James Bowes 已提交
332
		{ "gc", cmd_gc, RUN_SETUP },
J
Junio C Hamano 已提交
333
		{ "get-tar-commit-id", cmd_get_tar_commit_id },
J
Johannes Schindelin 已提交
334
		{ "grep", cmd_grep, RUN_SETUP | USE_PAGER },
J
Junio C Hamano 已提交
335
		{ "help", cmd_help },
336
		{ "init", cmd_init_db },
J
Junio C Hamano 已提交
337
		{ "init-db", cmd_init_db },
338 339 340
		{ "log", cmd_log, RUN_SETUP | USE_PAGER },
		{ "ls-files", cmd_ls_files, RUN_SETUP },
		{ "ls-tree", cmd_ls_tree, RUN_SETUP },
J
Junio C Hamano 已提交
341 342
		{ "mailinfo", cmd_mailinfo },
		{ "mailsplit", cmd_mailsplit },
J
Junio C Hamano 已提交
343
		{ "merge-base", cmd_merge_base, RUN_SETUP },
344
		{ "merge-file", cmd_merge_file },
345
		{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
346 347
		{ "name-rev", cmd_name_rev, RUN_SETUP },
		{ "pack-objects", cmd_pack_objects, RUN_SETUP },
348
		{ "pickaxe", cmd_blame, RUN_SETUP },
349 350
		{ "prune", cmd_prune, RUN_SETUP },
		{ "prune-packed", cmd_prune_packed, RUN_SETUP },
351
		{ "push", cmd_push, RUN_SETUP },
352
		{ "read-tree", cmd_read_tree, RUN_SETUP },
J
Junio C Hamano 已提交
353
		{ "reflog", cmd_reflog, RUN_SETUP },
354
		{ "repo-config", cmd_config },
J
Johannes Schindelin 已提交
355
		{ "rerere", cmd_rerere, RUN_SETUP },
356 357
		{ "rev-list", cmd_rev_list, RUN_SETUP },
		{ "rev-parse", cmd_rev_parse, RUN_SETUP },
358 359 360
		{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
		{ "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
		{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
J
Johannes Schindelin 已提交
361
		{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
362 363
		{ "show-branch", cmd_show_branch, RUN_SETUP },
		{ "show", cmd_show, RUN_SETUP | USE_PAGER },
J
Junio C Hamano 已提交
364
		{ "stripspace", cmd_stripspace },
365
		{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
R
Rene Scharfe 已提交
366
		{ "tar-tree", cmd_tar_tree },
367 368 369
		{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
		{ "update-index", cmd_update_index, RUN_SETUP },
		{ "update-ref", cmd_update_ref, RUN_SETUP },
F
Franck Bui-Huu 已提交
370
		{ "upload-archive", cmd_upload_archive },
J
Junio C Hamano 已提交
371
		{ "version", cmd_version },
372 373
		{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
		{ "write-tree", cmd_write_tree, RUN_SETUP },
R
Rene Scharfe 已提交
374
		{ "verify-pack", cmd_verify_pack },
375
		{ "show-ref", cmd_show_ref, RUN_SETUP },
376
		{ "pack-refs", cmd_pack_refs, RUN_SETUP },
377 378 379
	};
	int i;

L
Linus Torvalds 已提交
380 381 382 383 384 385
	/* Turn "git cmd --help" into "git help cmd" */
	if (argc > 1 && !strcmp(argv[1], "--help")) {
		argv[1] = argv[0];
		argv[0] = cmd = "help";
	}

386 387 388 389
	for (i = 0; i < ARRAY_SIZE(commands); i++) {
		struct cmd_struct *p = commands+i;
		if (strcmp(p->cmd, cmd))
			continue;
390
		exit(run_command(p, argc, argv));
391 392 393
	}
}

394
int main(int argc, const char **argv)
395
{
D
Dmitry V. Levin 已提交
396
	const char *cmd = argv[0] ? argv[0] : "git-help";
397 398
	char *slash = strrchr(cmd, '/');
	const char *exec_path = NULL;
J
Junio C Hamano 已提交
399
	int done_alias = 0;
400 401 402 403 404 405 406 407 408 409 410 411 412

	/*
	 * 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;
	}
413

414 415 416 417 418 419 420 421 422 423
	/*
	 * "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.
	 */
424
	if (!prefixcmp(cmd, "git-")) {
425 426
		cmd += 4;
		argv[0] = cmd;
427
		handle_internal_command(argc, argv);
428 429
		die("cannot handle %s internally", cmd);
	}
430

431
	/* Look for flags.. */
432 433
	argv++;
	argc--;
434
	handle_options(&argv, &argc, NULL);
435
	if (argc > 0) {
436
		if (!prefixcmp(argv[0], "--"))
437 438 439 440 441
			argv[0] += 2;
	} else {
		/* Default command: "help" */
		argv[0] = "help";
		argc = 1;
442
	}
443
	cmd = argv[0];
444 445

	/*
J
Junio C Hamano 已提交
446 447 448 449 450
	 * We execute external git command via execv_git_cmd(),
	 * which looks at "--exec-path" option, GIT_EXEC_PATH
	 * environment, and $(gitexecdir) in Makefile while built,
	 * in this order.  For scripted commands, we prepend
	 * the value of the exec_path variable to the PATH.
451 452 453
	 */
	if (exec_path)
		prepend_to_path(exec_path, strlen(exec_path));
454 455
	exec_path = git_exec_path();
	prepend_to_path(exec_path, strlen(exec_path));
456

J
Junio C Hamano 已提交
457 458
	while (1) {
		/* See if it's an internal command */
459
		handle_internal_command(argc, argv);
460

J
Junio C Hamano 已提交
461 462
		/* .. then try the external ones */
		execv_git_cmd(argv);
463

J
Junio C Hamano 已提交
464 465 466 467 468 469 470 471
		/* 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;
	}
472

473 474 475 476 477 478 479
	if (errno == ENOENT) {
		if (done_alias) {
			fprintf(stderr, "Expansion of alias '%s' failed; "
				"'%s' is not a git-command\n",
				cmd, argv[0]);
			exit(1);
		}
480
		help_unknown_cmd(cmd);
481
	}
482 483

	fprintf(stderr, "Failed to run command '%s': %s\n",
484
		cmd, strerror(errno));
485 486 487

	return 1;
}