git.c 13.1 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|--no-pager] [--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 62 63 64
		} else if (!strcmp(cmd, "--no-pager")) {
			setenv("GIT_PAGER", "cat", 1);
			if (envchanged)
				*envchanged = 1;
65
		} else if (!strcmp(cmd, "--git-dir")) {
66 67 68 69
			if (*argc < 2) {
				fprintf(stderr, "No directory given for --git-dir.\n" );
				usage(git_usage_string);
			}
70
			setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
71 72
			if (envchanged)
				*envchanged = 1;
73 74
			(*argv)++;
			(*argc)--;
75
			handled++;
76
		} else if (!prefixcmp(cmd, "--git-dir=")) {
77
			setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
78 79
			if (envchanged)
				*envchanged = 1;
80 81 82 83 84 85
		} 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);
86 87
			if (envchanged)
				*envchanged = 1;
88 89 90 91
			(*argv)++;
			(*argc)--;
		} else if (!prefixcmp(cmd, "--work-tree=")) {
			setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
92 93
			if (envchanged)
				*envchanged = 1;
94
		} else if (!strcmp(cmd, "--bare")) {
95
			static char git_dir[PATH_MAX+1];
96
			is_bare_repository_cfg = 1;
97
			setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
98 99
			if (envchanged)
				*envchanged = 1;
100 101
		} else {
			fprintf(stderr, "Unknown option: %s\n", cmd);
102
			usage(git_usage_string);
103
		}
104 105 106 107 108 109 110 111

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

112
static const char *alias_command;
113
static char *alias_string;
114 115 116

static int git_alias_config(const char *var, const char *value)
{
117
	if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
118
		alias_string = xstrdup(value);
119 120 121 122 123 124 125 126 127
	}
	return 0;
}

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

J
James Bowes 已提交
128
	*argv = xmalloc(sizeof(char*) * size);
129 130 131 132 133 134 135 136 137 138 139 140

	/* 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 已提交
141
				*argv = xrealloc(*argv, sizeof(char*) * size);
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 173 174 175 176 177
			}
			(*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)
{
178
	int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
179
	const char *subdir;
180 181
	int count, option_count;
	const char** new_argv;
182 183

	subdir = setup_git_directory_gently(&nongit);
184 185 186 187

	alias_command = (*argv)[0];
	git_config(git_alias_config);
	if (alias_string) {
188
		if (alias_string[0] == '!') {
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
			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");
			}
204 205 206 207 208 209 210 211 212
			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);
		}
213
		count = split_cmdline(alias_string, &new_argv);
214 215 216 217 218
		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);
219 220 221 222 223 224 225 226 227 228
		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);

229 230 231
		trace_argv_printf(new_argv, count,
				  "trace: alias expansion: %s =>",
				  alias_command);
232

J
Jonas Fonseca 已提交
233 234
		new_argv = xrealloc(new_argv, sizeof(char*) *
				    (count + *argcp + 1));
235 236 237
		/* insert after command name */
		memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
		new_argv[count+*argcp] = NULL;
238

239 240
		*argv = new_argv;
		*argcp += count - 1;
241

242
		ret = 1;
243 244 245 246 247
	}

	if (subdir)
		chdir(subdir);

248 249
	errno = saved_errno;

250 251 252
	return ret;
}

253
const char git_version_string[] = GIT_VERSION;
J
Junio C Hamano 已提交
254

255 256
#define RUN_SETUP	(1<<0)
#define USE_PAGER	(1<<1)
257 258 259 260
/*
 * require working tree to be present -- anything uses this needs
 * RUN_SETUP for reading from the configuration file.
 */
261
#define NEED_WORK_TREE	(1<<2)
262

263 264 265 266 267 268 269 270
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)
{
271 272
	int status;
	struct stat st;
273 274 275 276 277 278 279
	const char *prefix;

	prefix = NULL;
	if (p->option & RUN_SETUP)
		prefix = setup_git_directory();
	if (p->option & USE_PAGER)
		setup_pager();
280 281 282 283 284 285 286 287
	if (p->option & NEED_WORK_TREE) {
		const char *work_tree = get_git_work_tree();
		const char *git_dir = get_git_dir();
		if (!is_absolute_path(git_dir))
			set_git_dir(make_absolute_path(git_dir));
		if (!work_tree || chdir(work_tree))
			die("%s must be run in a work tree", p->cmd);
	}
288 289
	trace_argv_printf(argv, argc, "trace: built-in: git");

290 291 292 293 294 295 296 297 298 299 300 301
	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.. */
302
	if (fflush(stdout))
303
		die("write failure on standard output: %s", strerror(errno));
304 305 306 307
	if (ferror(stdout))
		die("unknown write failure on standard output");
	if (fclose(stdout))
		die("close failed on standard output: %s", strerror(errno));
308
	return 0;
309 310 311
}

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

L
Linus Torvalds 已提交
393 394 395 396 397 398
	/* Turn "git cmd --help" into "git help cmd" */
	if (argc > 1 && !strcmp(argv[1], "--help")) {
		argv[1] = argv[0];
		argv[0] = cmd = "help";
	}

399 400 401 402
	for (i = 0; i < ARRAY_SIZE(commands); i++) {
		struct cmd_struct *p = commands+i;
		if (strcmp(p->cmd, cmd))
			continue;
403
		exit(run_command(p, argc, argv));
404 405 406
	}
}

407
int main(int argc, const char **argv)
408
{
D
Dmitry V. Levin 已提交
409
	const char *cmd = argv[0] ? argv[0] : "git-help";
410 411
	char *slash = strrchr(cmd, '/');
	const char *exec_path = NULL;
J
Junio C Hamano 已提交
412
	int done_alias = 0;
413 414 415 416 417 418 419 420 421 422 423 424 425

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

427 428 429 430 431 432 433 434 435 436
	/*
	 * "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.
	 */
437
	if (!prefixcmp(cmd, "git-")) {
438 439
		cmd += 4;
		argv[0] = cmd;
440
		handle_internal_command(argc, argv);
441 442
		die("cannot handle %s internally", cmd);
	}
443

444
	/* Look for flags.. */
445 446
	argv++;
	argc--;
447
	handle_options(&argv, &argc, NULL);
448
	if (argc > 0) {
449
		if (!prefixcmp(argv[0], "--"))
450 451 452 453 454
			argv[0] += 2;
	} else {
		/* Default command: "help" */
		argv[0] = "help";
		argc = 1;
455
	}
456
	cmd = argv[0];
457 458

	/*
J
Junio C Hamano 已提交
459 460 461 462 463
	 * 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.
464 465 466
	 */
	if (exec_path)
		prepend_to_path(exec_path, strlen(exec_path));
467 468
	exec_path = git_exec_path();
	prepend_to_path(exec_path, strlen(exec_path));
469

J
Junio C Hamano 已提交
470 471
	while (1) {
		/* See if it's an internal command */
472
		handle_internal_command(argc, argv);
473

J
Junio C Hamano 已提交
474 475
		/* .. then try the external ones */
		execv_git_cmd(argv);
476

J
Junio C Hamano 已提交
477 478 479 480 481 482 483 484
		/* 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;
	}
485

486 487 488 489 490 491 492
	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);
		}
493
		help_unknown_cmd(cmd);
494
	}
495 496

	fprintf(stderr, "Failed to run command '%s': %s\n",
497
		cmd, strerror(errno));
498 499 500

	return 1;
}