git.c 11.4 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] [--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 32 33 34 35 36 37 38 39
static int handle_options(const char*** argv, int* argc)
{
	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
			(*argv)++;
			(*argc)--;
69
			handled++;
70
		} else if (!prefixcmp(cmd, "--git-dir=")) {
71
			setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
72
		} else if (!strcmp(cmd, "--bare")) {
73
			static char git_dir[PATH_MAX+1];
74
			setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
75 76
		} else {
			fprintf(stderr, "Unknown option: %s\n", cmd);
77
			usage(git_usage_string);
78
		}
79 80 81 82 83 84 85 86

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

87
static const char *alias_command;
88
static char *alias_string;
89 90 91

static int git_alias_config(const char *var, const char *value)
{
92
	if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
93
		alias_string = xstrdup(value);
94 95 96 97 98 99 100 101 102
	}
	return 0;
}

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

J
James Bowes 已提交
103
	*argv = xmalloc(sizeof(char*) * size);
104 105 106 107 108 109 110 111 112 113 114 115

	/* 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 已提交
116
				*argv = xrealloc(*argv, sizeof(char*) * size);
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
			}
			(*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)
{
153
	int nongit = 0, ret = 0, saved_errno = errno;
154
	const char *subdir;
155 156
	int count, option_count;
	const char** new_argv;
157 158

	subdir = setup_git_directory_gently(&nongit);
159 160 161 162

	alias_command = (*argv)[0];
	git_config(git_alias_config);
	if (alias_string) {
163 164 165 166 167 168 169 170 171 172
		if (alias_string[0] == '!') {
			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);
		}
173 174 175 176 177 178 179 180 181 182 183 184
		count = split_cmdline(alias_string, &new_argv);
		option_count = handle_options(&new_argv, &count);
		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);

185 186 187
		trace_argv_printf(new_argv, count,
				  "trace: alias expansion: %s =>",
				  alias_command);
188

J
Jonas Fonseca 已提交
189 190
		new_argv = xrealloc(new_argv, sizeof(char*) *
				    (count + *argcp + 1));
191 192 193
		/* insert after command name */
		memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
		new_argv[count+*argcp] = NULL;
194

195 196
		*argv = new_argv;
		*argcp += count - 1;
197

198
		ret = 1;
199 200 201 202 203
	}

	if (subdir)
		chdir(subdir);

204 205
	errno = saved_errno;

206 207 208
	return ret;
}

209
const char git_version_string[] = GIT_VERSION;
J
Junio C Hamano 已提交
210

211 212
#define RUN_SETUP	(1<<0)
#define USE_PAGER	(1<<1)
213 214 215 216 217
/*
 * require working tree to be present -- anything uses this needs
 * RUN_SETUP for reading from the configuration file.
 */
#define NOT_BARE 	(1<<2)
218

219 220 221 222 223 224 225 226
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)
{
227 228
	int status;
	struct stat st;
229 230 231 232 233 234 235 236 237 238 239 240 241
	const char *prefix;

	prefix = NULL;
	if (p->option & RUN_SETUP)
		prefix = setup_git_directory();
	if (p->option & USE_PAGER)
		setup_pager();
	if (p->option & NOT_BARE) {
		if (is_bare_repository() || is_inside_git_dir())
			die("%s must be run in a work tree", p->cmd);
	}
	trace_argv_printf(argv, argc, "trace: built-in: git");

242 243 244 245 246 247 248 249 250 251 252 253
	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.. */
254
	if (fflush(stdout))
255
		die("write failure on standard output: %s", strerror(errno));
256 257 258 259
	if (ferror(stdout))
		die("unknown write failure on standard output");
	if (fclose(stdout))
		die("close failed on standard output: %s", strerror(errno));
260
	return 0;
261 262 263
}

static void handle_internal_command(int argc, const char **argv)
264 265
{
	const char *cmd = argv[0];
266
	static struct cmd_struct commands[] = {
267
		{ "add", cmd_add, RUN_SETUP | NOT_BARE },
268
		{ "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
J
Junio C Hamano 已提交
269
		{ "apply", cmd_apply },
270
		{ "archive", cmd_archive },
271
		{ "blame", cmd_blame, RUN_SETUP },
J
Junio C Hamano 已提交
272
		{ "branch", cmd_branch, RUN_SETUP },
273
		{ "bundle", cmd_bundle },
274 275
		{ "cat-file", cmd_cat_file, RUN_SETUP },
		{ "checkout-index", cmd_checkout_index, RUN_SETUP },
P
Peter Eriksen 已提交
276
		{ "check-ref-format", cmd_check_ref_format },
277
		{ "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
R
Rene Scharfe 已提交
278
		{ "cherry", cmd_cherry, RUN_SETUP },
279
		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
280
		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
281
		{ "config", cmd_config },
282
		{ "count-objects", cmd_count_objects, RUN_SETUP },
S
Shawn O. Pearce 已提交
283
		{ "describe", cmd_describe, RUN_SETUP },
284 285
		{ "diff", cmd_diff, USE_PAGER },
		{ "diff-files", cmd_diff_files },
286 287
		{ "diff-index", cmd_diff_index, RUN_SETUP },
		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
288
		{ "fetch--tool", cmd_fetch__tool, RUN_SETUP },
289
		{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
290
		{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
291
		{ "format-patch", cmd_format_patch, RUN_SETUP },
292 293
		{ "fsck", cmd_fsck, RUN_SETUP },
		{ "fsck-objects", cmd_fsck, RUN_SETUP },
J
James Bowes 已提交
294
		{ "gc", cmd_gc, RUN_SETUP },
J
Junio C Hamano 已提交
295
		{ "get-tar-commit-id", cmd_get_tar_commit_id },
J
Johannes Schindelin 已提交
296
		{ "grep", cmd_grep, RUN_SETUP | USE_PAGER },
J
Junio C Hamano 已提交
297
		{ "help", cmd_help },
298
		{ "init", cmd_init_db },
J
Junio C Hamano 已提交
299
		{ "init-db", cmd_init_db },
300 301 302
		{ "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 已提交
303 304
		{ "mailinfo", cmd_mailinfo },
		{ "mailsplit", cmd_mailsplit },
J
Junio C Hamano 已提交
305
		{ "merge-base", cmd_merge_base, RUN_SETUP },
306
		{ "merge-file", cmd_merge_file },
307
		{ "mv", cmd_mv, RUN_SETUP | NOT_BARE },
308 309
		{ "name-rev", cmd_name_rev, RUN_SETUP },
		{ "pack-objects", cmd_pack_objects, RUN_SETUP },
J
Junio C Hamano 已提交
310
		{ "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
311 312
		{ "prune", cmd_prune, RUN_SETUP },
		{ "prune-packed", cmd_prune_packed, RUN_SETUP },
313
		{ "push", cmd_push, RUN_SETUP },
314
		{ "read-tree", cmd_read_tree, RUN_SETUP },
J
Junio C Hamano 已提交
315
		{ "reflog", cmd_reflog, RUN_SETUP },
316
		{ "repo-config", cmd_config },
J
Johannes Schindelin 已提交
317
		{ "rerere", cmd_rerere, RUN_SETUP },
318 319
		{ "rev-list", cmd_rev_list, RUN_SETUP },
		{ "rev-parse", cmd_rev_parse, RUN_SETUP },
320
		{ "revert", cmd_revert, RUN_SETUP | NOT_BARE },
321 322
		{ "rm", cmd_rm, RUN_SETUP | NOT_BARE },
		{ "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
J
Johannes Schindelin 已提交
323
		{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
324 325
		{ "show-branch", cmd_show_branch, RUN_SETUP },
		{ "show", cmd_show, RUN_SETUP | USE_PAGER },
J
Junio C Hamano 已提交
326
		{ "stripspace", cmd_stripspace },
327
		{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
R
Rene Scharfe 已提交
328
		{ "tar-tree", cmd_tar_tree },
329 330 331
		{ "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 已提交
332
		{ "upload-archive", cmd_upload_archive },
J
Junio C Hamano 已提交
333
		{ "version", cmd_version },
334 335
		{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
		{ "write-tree", cmd_write_tree, RUN_SETUP },
R
Rene Scharfe 已提交
336
		{ "verify-pack", cmd_verify_pack },
337
		{ "show-ref", cmd_show_ref, RUN_SETUP },
338
		{ "pack-refs", cmd_pack_refs, RUN_SETUP },
339 340 341
	};
	int i;

L
Linus Torvalds 已提交
342 343 344 345 346 347
	/* Turn "git cmd --help" into "git help cmd" */
	if (argc > 1 && !strcmp(argv[1], "--help")) {
		argv[1] = argv[0];
		argv[0] = cmd = "help";
	}

348 349 350 351
	for (i = 0; i < ARRAY_SIZE(commands); i++) {
		struct cmd_struct *p = commands+i;
		if (strcmp(p->cmd, cmd))
			continue;
352
		exit(run_command(p, argc, argv));
353 354 355
	}
}

356
int main(int argc, const char **argv)
357
{
D
Dmitry V. Levin 已提交
358
	const char *cmd = argv[0] ? argv[0] : "git-help";
359 360
	char *slash = strrchr(cmd, '/');
	const char *exec_path = NULL;
J
Junio C Hamano 已提交
361
	int done_alias = 0;
362 363 364 365 366 367 368 369 370 371 372 373 374

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

376 377 378 379 380 381 382 383 384 385
	/*
	 * "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.
	 */
386
	if (!prefixcmp(cmd, "git-")) {
387 388
		cmd += 4;
		argv[0] = cmd;
389
		handle_internal_command(argc, argv);
390 391
		die("cannot handle %s internally", cmd);
	}
392

393
	/* Look for flags.. */
394 395 396 397
	argv++;
	argc--;
	handle_options(&argv, &argc);
	if (argc > 0) {
398
		if (!prefixcmp(argv[0], "--"))
399 400 401 402 403
			argv[0] += 2;
	} else {
		/* Default command: "help" */
		argv[0] = "help";
		argc = 1;
404
	}
405
	cmd = argv[0];
406 407 408 409 410 411 412 413 414 415

	/*
	 * 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));
416 417
	exec_path = git_exec_path();
	prepend_to_path(exec_path, strlen(exec_path));
418

J
Junio C Hamano 已提交
419 420
	while (1) {
		/* See if it's an internal command */
421
		handle_internal_command(argc, argv);
422

J
Junio C Hamano 已提交
423 424
		/* .. then try the external ones */
		execv_git_cmd(argv);
425

J
Junio C Hamano 已提交
426 427 428 429 430 431 432 433
		/* 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;
	}
434

435 436 437 438 439 440 441
	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);
		}
442
		help_unknown_cmd(cmd);
443
	}
444 445

	fprintf(stderr, "Failed to run command '%s': %s\n",
446
		cmd, strerror(errno));
447 448 449

	return 1;
}