[PATCH/RFC 8/8] Make git-am a builtin

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Lukas Sandström <lukass@xxxxxxxxxxxxxxxx>
---

Being able to switch index-file on the fly would reduce the number of
system() calls.
A way to check if the index/working dir is dirty would also help.

 Makefile     |    6 -
 builtin-am.c |  664 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h    |    1 
 git-am.sh    |  427 -------------------------------------
 git.c        |    3 
 5 files changed, 670 insertions(+), 431 deletions(-)

diff --git a/Makefile b/Makefile
index 4b30ca0..e9b372e 100644
--- a/Makefile
+++ b/Makefile
@@ -122,7 +122,7 @@ SCRIPT_SH = \
 	git-repack.sh git-request-pull.sh git-reset.sh \
 	git-resolve.sh git-revert.sh git-sh-setup.sh \
 	git-tag.sh git-verify-tag.sh \
-	git-applymbox.sh git-applypatch.sh git-am.sh \
+	git-applymbox.sh git-applypatch.sh \
 	git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
 	git-merge-resolve.sh git-merge-ours.sh \
 	git-lost-found.sh git-quiltimport.sh
@@ -166,7 +166,7 @@ PROGRAMS = \
 BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \
 	git-count-objects$X git-diff$X git-push$X git-mailsplit$X \
 	git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \
-	git-check-ref-format$X git-rev-parse$X git-mailinfo$X \
+	git-check-ref-format$X git-rev-parse$X git-mailinfo$X git-am$X \
 	git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \
 	git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \
 	git-read-tree$X git-commit-tree$X git-write-tree$X \
@@ -220,7 +220,7 @@ LIB_OBJS = \
 BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \
-	builtin-rm.o builtin-init-db.o builtin-rev-parse.o \
+	builtin-rm.o builtin-init-db.o builtin-rev-parse.o builtin-am.o \
 	builtin-tar-tree.o builtin-upload-tar.o builtin-update-index.o \
 	builtin-ls-files.o builtin-ls-tree.o builtin-write-tree.o \
 	builtin-read-tree.o builtin-commit-tree.o builtin-mailinfo.o \
diff --git a/builtin-am.c b/builtin-am.c
new file mode 100644
index 0000000..d9e7ac5
--- /dev/null
+++ b/builtin-am.c
@@ -0,0 +1,664 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Lukas Sandström, 2006
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/wait.h>
+
+#include "git-compat-util.h"
+#include "cache.h"
+#include "builtin.h"
+
+static char builtin_am_usage[] = "[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] "
+				 "[--interactive] [--whitespace=<option>] <mbox>...\n"
+				 "or, when resuming [--skip | --resolved]";
+
+static int binary, interactive, threeway, signoff, utf8, keep_subject, resolved, skip, resume;
+static char whitespace[40] = "--whitespace=warn", **env;
+static const char **mbox, *dotest, *resolvmsg;
+
+#define PATCH_PREC 4
+
+#define AGAIN 0
+#define SKIP 1
+#define YES 2
+
+//ugly hack to be able to change the index file
+extern char *git_index_file;
+
+static int rm_rf(const char* path)
+{
+	char cmd[PATH_MAX + 10];
+	snprintf(cmd, sizeof(cmd), "rm -rf %s", path);
+	return system(cmd);
+}
+
+static int mkdir_p(const char *path)
+{
+	char p[PATH_MAX], n, *l;
+
+	strcpy(p, path);
+	while ((l = strchr(p, '/'))) {
+		n = *l;
+		*l = '\0';
+		if (access(p, F_OK) && mkdir(p, 0777))
+			return -1;
+		*l = n;
+	}
+	return mkdir(p, 0777);
+}
+
+static int fcat(char *file, char *fmt, ...)
+{
+	va_list args;
+	int ret;
+	FILE *f;
+
+	file = mkpath("%s/%s", dotest, file);
+	if ((f = fopen(file, "r")) == NULL) {
+		perror(file);
+		die("Couldn't open file %s", file);
+	}
+	va_start(args, fmt);
+	ret = vfscanf(f, fmt, args);
+	va_end(args);
+	fclose(f);
+	return ret;
+}
+
+static int fecho(char *file, char *fmt, ...)
+{
+	va_list args;
+	int ret;
+	FILE *f;
+
+	file = mkpath("%s/%s", dotest, file);
+	if ((f = fopen(file, "w")) == NULL) {
+		perror(file);
+		die("Couldn't open file %s/%s", dotest, file);
+	}
+	va_start(args, fmt);
+	ret = vfprintf(f, fmt, args);
+	va_end(args);
+	fclose(f);
+	return ret;
+}
+
+static FILE* get_output(char *cmd, int *status)
+{
+	char c[2000];
+	FILE *ret;
+	int s;
+
+	snprintf(c, sizeof(c), "%s > \"%s/outtmp\"", cmd, dotest);
+	s = system(c);
+	if (status)
+		*status = s;
+	if ((ret = fopen(mkpath("%s/outtmp", dotest), "r")) == NULL)
+		die("cmd: %s\nOpen \"%s\" failed.", c, mkpath("%s/outtmp", dotest));
+	unlink(mkpath("%s/outtmp", dotest));
+	return ret;
+}
+
+static int has_zero_output(char *cmd)
+{
+	struct stat s;
+
+	system(mkpath("%s > %s/zerotmp", cmd, dotest));
+	stat(mkpath("%s/zerotmp", dotest), &s);
+	unlink(mkpath("%s/zerotmp", dotest));
+	return s.st_size == 0;
+}
+
+static int go_next(int this) {
+	unlink(mkpath("%s/%0*d", dotest, PATCH_PREC, this));
+	unlink(mkpath("%s/msg", dotest));
+	unlink(mkpath("%s/msg-clean", dotest));
+	unlink(mkpath("%s/patch", dotest));
+	unlink(mkpath("%s/info", dotest));
+	fecho("next", "%d", this + 1);
+	return this + 1;
+}
+
+static void stop_here(int this)
+{
+	fecho("next","%d\n", this);
+	exit(1);
+}
+
+static void stop_here_user_resolve(int this)
+{
+	char cmdline[1000] = "git am";
+	int pos = 6; /* "git am" */
+
+	if (resolvmsg != NULL) {
+		printf("%s", resolvmsg);
+		stop_here(this);
+	}
+
+	if (interactive)
+		pos += sprintf(cmdline + pos, " -i");
+	if (threeway)
+		pos += sprintf(cmdline + pos, " -3");
+	if (strcmp(".dotest", dotest))
+		pos += sprintf(cmdline + pos, " -d=%s", dotest);
+
+	printf("When you have resolved this problem run \"git am %s --resolved\".\n", cmdline);
+	printf("If you would prefer to skip this patch, instead run \"%s --skip\".\n", cmdline);
+
+	stop_here(this);
+}
+
+static int fall_back_3way()
+{
+	char cmd[1000];
+	char tmp_index[PATH_MAX], old_index[PATH_MAX] = "";
+	int ret = -1;
+
+	snprintf(cmd, sizeof(cmd), "git-apply -z --index-info \"%s/patch\""
+			" > %s/patch-merge-index-info 2> /dev/null", dotest, dotest);
+	if (!system(cmd)) {
+		snprintf(tmp_index, sizeof(tmp_index),"%s/patch-merge-tmp-index", dotest);
+		if (getenv(INDEX_ENVIRONMENT))
+			strcpy(old_index, getenv(INDEX_ENVIRONMENT));
+		setenv(INDEX_ENVIRONMENT, tmp_index, 1);
+
+		snprintf(cmd, sizeof(cmd), "git-update-index -z --index-info <\"%s/patch-merge-index-info\"", dotest);
+		if (!system(cmd)) {
+#if 1
+			system(mkpath("git-write-tree > \"%s/patch-merge-base\"", dotest));
+			snprintf(cmd, sizeof(cmd), "git-apply %s --cached < \"%s/patch\"", binary ? "--allow-binary-replacement":"", dotest);
+			if (!system(cmd)) {
+				char his_tree[41], orig_tree[41];
+				printf("Using index info to reconstruct a base tree...\n");
+				system(mkpath("git-write-tree > \"%s/his-tree\"", dotest));
+				fcat("his-tree", "%40s", his_tree);
+				fcat("patch-merge-base", "%40s", orig_tree);
+
+				printf("Falling back to patching base and 3-way merge...\n");
+
+				if (*old_index)
+					setenv(INDEX_ENVIRONMENT, old_index, 1);
+				else
+					unsetenv(INDEX_ENVIRONMENT);
+				if (!system(mkpath("git-merge-resolve %s -- HEAD %s", orig_tree, his_tree)))
+					return 0;
+			}
+		}
+#else
+			unsigned char orig_tree[20], his_tree[20];
+
+			// We need a way to switch the index file on the fly for this to work
+
+			char *opts[] = { "git-apply", "--allow-binary-replacement", "--cached", NULL, NULL };
+			char **opt = &opts[0];
+			char patch[PATH_MAX];
+			int optc = ARRAY_SIZE(opts) - 1;
+
+			opts[optc - 1] = strncpy(patch, mkpath("%s/patch", dotest), sizeof(patch));
+			if (!binary) {
+				opts[1] = "git-apply";
+				opt++; optc--;
+			}
+			write_tree(orig_tree, 0, NULL);
+			if (!cmd_apply(optc, (const char**)opt, env)) {
+				printf("Using index info to reconstruct a base tree...\n");
+				write_tree(his_tree, 0, NULL);
+
+				snprintf(cmd, sizeof(cmd), "git-merge-resolve %s -- HEAD %s", sha1_to_hex(orig_tree),
+					 sha1_to_hex(his_tree));
+				ret = system(cmd);
+			}
+		}
+		if (*old_index)
+			setenv(INDEX_ENVIRONMENT, old_index, 1);
+		else
+			unsetenv(INDEX_ENVIRONMENT);
+#endif
+		if (!ret)
+			return 0;
+	}
+	if (!access(mkpath("%s/rr-cache/.", get_git_dir()), F_OK))
+		system("git-rerere");
+	die("Failed to merge in the changes.");
+}
+
+static int go_interactive(void)
+{
+	int action = AGAIN;
+
+	if (!isatty(0))
+		die("Cannot be interactive without stdin connected to a terminal.");
+
+	while (action == AGAIN) {
+		char line[1000];
+		FILE *cmt;
+
+		printf("Commit Body is:\n--------------------------\n");
+		cmt = fopen(mkpath("%s/final-commit", dotest), "r");
+		while (fgets(line, sizeof(line), cmt))
+			fputs(line, stdout);
+		fclose(cmt);
+		printf("--------------------------\nApply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all ");
+
+		fgets(line, sizeof(line), stdin);
+		switch (line[0]) {
+			case 'y':
+			case 'Y':
+				action = YES;
+				break;
+			case 'a':
+			case 'A':
+				action = YES;
+				interactive = 0;
+				break;
+			case 'n':
+			case 'N':
+				action = SKIP;
+				break;
+			case 'e':
+			case 'E':
+				system(mkpath("\"${VISUAL:-${EDITOR:-vi}}\" \"%s/final-commit\"", dotest));
+				action = AGAIN;
+				break;
+			case 'v':
+			case 'V':
+				system(mkpath("LESS=-S ${PAGER:-less} \"%s/patch\"", dotest));
+				action = AGAIN;
+				break;
+			default:
+				action = AGAIN;
+				break;
+		}
+	}
+	return action;
+}
+
+static int commit(char *subject)
+{
+	unsigned char sha1[20];
+	char commit[41], parent[41], cmd[1000];
+	FILE *f;
+	int status;
+
+	if (!write_tree(sha1, 0, NULL)) {
+		printf("Wrote tree %s\n", sha1_to_hex(sha1));
+		f = get_output("git-rev-parse --verify HEAD", &status);
+		if (!status) {
+			fgets(parent, 41, f);
+			fclose(f);
+			snprintf(cmd, sizeof(cmd), "git-commit-tree %s -p %s <\"%s/final-commit\"",
+				 sha1_to_hex(sha1), parent, dotest);
+			f = get_output(cmd, &status);
+			if (!status) {
+				//git-update-ref -m "am: $SUBJECT" HEAD $commit $parent
+				char *opts[] = { "git-update-ref", "-m", NULL, "HEAD", NULL, NULL, NULL };
+				const char **opt = (const char**)&opts[0];
+				fgets(commit, 41, f);
+				fclose(f);
+				printf("Committed: %s\n", commit);
+				snprintf(cmd, sizeof(cmd), "am: %s", subject);
+				opts[2] = cmd;
+				opts[4] = commit;
+				opts[5] = parent;
+				if (!cmd_update_ref(ARRAY_SIZE(opts) - 1, opt, env))
+					return 0;
+			}
+		}
+	}
+	return -1;
+}
+
+int cmd_am(int argc, const char **argv, char **envp)
+{
+	int i, this, last, apply_status, action;
+	char sign[1000] = "";
+
+	env = envp;
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+
+		if (arg[0] != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strcmp(arg, "-i") || !strcmp(arg, "--interactive")) {
+			interactive = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-b") || !strcmp(arg, "--binary")) {
+			binary = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-3") || !strcmp(arg, "--3way")) {
+			threeway = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-s") || !strcmp(arg, "--signoff")) {
+			signoff = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--skip")) {
+			skip = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-u") || !strcmp(arg, "--utf8")) {
+			utf8 = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-k") || !strcmp(arg, "--keep")) {
+			keep_subject = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-r") || !strcmp(arg, "--resolved")) {
+			resolved = 1;
+			continue;
+		}
+		if (!strncmp(arg, "--whitespace=", 13)) {
+			strncpy(whitespace, arg, sizeof(whitespace));
+			continue;
+		}
+		if (!strncmp(arg, "--resolvemsg=", 13)) {
+			resolvmsg = arg + 13;
+			continue;
+		}
+		if (!strncmp(arg, "--dotest", 8)) {
+			if (arg[8] == '=')
+				dotest = arg + 9;
+			else {
+				i++;
+				if (argv[i] == NULL)
+					die(builtin_am_usage);
+				dotest = argv[i];
+			}
+			continue;
+		}
+		usage(builtin_am_usage);
+	}
+	mbox = argv + i;
+
+	if (!dotest)
+		dotest = ".dotest";
+
+	/* Cleanup old .dotest */
+	if (mbox && !access(dotest, F_OK))
+		if (fcat("next", "%d", &this) && fcat("last", "%d",  &last))
+			if (this > last)
+				rm_rf(dotest);
+
+	if (!access(dotest, F_OK)) {
+		if (mbox != NULL)
+			die("previous dotest directory \"%s\" still exists but mbox given.", dotest);
+		resume = 1;
+	} else {
+		if (skip || resolved)
+			die("Resolve operation not in progress, we are not resuming.");
+
+		if (mkdir_p(dotest))
+			die("Unable to create directory %s.", dotest);
+
+		if ((last = split_mbox(mbox, dotest, 1 /*allow bare*/, PATCH_PREC, 0 /*skip*/)) == -1) {
+			rm_rf(dotest);
+			die("split_mbox failed");
+		}
+
+		/*
+		  -b, -s, -u, -k and --whitespace flags are kept for the
+		  resuming session after a patch failure.
+		  -3 and -i can and must be given when resuming.
+		*/
+		fecho("binary", "%d\n", binary);
+		fecho("whitespace", "%s\n", whitespace);
+		fecho("sign", "%d\n", signoff);
+		fecho("utf8", "%d\n", utf8);
+		fecho("keep", "%d\n", keep_subject);
+		fecho("next", "%d\n", 1);
+		fecho("last", "%d\n", last);
+	}
+
+	if (!resolved) {
+		/* Make sure we have a clean index */
+		char buf[PATH_MAX];
+		int status = 0;
+		FILE *f;
+
+		if ((f = get_output("git-diff-index --name-only HEAD", &status)) == NULL || status)
+			die("Command: \"git-diff-index --name-only HEAD\" failed");
+
+		if ((status = fgetc(f)) != EOF) {
+			ungetc(status, f);
+			fprintf(stderr, "Dirty index: cannot apply patches. Dirty files:\n");
+			while (fgets(buf, sizeof(buf), f))
+				fprintf(stderr, "%s", buf);
+			return 1;
+		}
+		fclose(f);
+	}
+
+	/* Read back saved state */
+	fcat("binary", "%d", &binary);
+	fcat("utf8", "%d", &utf8);
+	fcat("keep", "%d", &keep_subject);
+	fcat("whitespace", "%40[^\n]", whitespace);
+	fcat("sign", "%d", &signoff);
+	fcat("last", "%d", &last);
+	fcat("next", "%d", &this);
+
+	if (this > last) {
+		printf("Nothing to do.\n");
+		rm_rf(dotest);
+		return 0;
+	}
+
+	if (signoff) {
+		int off = snprintf(sign, sizeof(sign), "Signed-off-by: %s <%s>",
+				   getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"));
+		if (off > sizeof(sign))
+			die ("Impossibly long committer identifier");
+	}
+
+	if (skip) {
+		this++;
+		resume = 0;
+	}
+
+	while (this <= last) {
+		char patch_no[PATCH_PREC + 1];
+		char name[1000];
+		char email[1000];
+		char date[1000];
+		char s[1000] = "[PATCH] ", *subject = &s[0];
+
+		snprintf(patch_no, sizeof(patch_no), "%0*d", PATCH_PREC, this);
+
+		if (access(mkpath("%s/%s", dotest, patch_no), F_OK)) {
+			resume = 0;
+			this = go_next(this);
+			continue;
+		}
+
+		/*
+		  If we are not resuming, parse and extract the patch information
+		  into separate files:
+		  - info records the authorship and title
+		  - msg is the rest of commit log message
+		  - patch is the patch body.
+
+		 When we are resuming, these files are either already prepared
+		 by the user, or the user can tell us to do so by --resolved flag.
+		*/
+		if (!resume) {
+			FILE *out, *in;
+			char msg_path[PATH_MAX];
+
+			if ((out = fopen(mkpath("%s/info", dotest), "w")) == NULL) {
+				perror(mkpath("%s/info", dotest));
+				die("fopen failed");
+			}
+			if ((in = fopen(mkpath("%s/%s", dotest, patch_no), "r")) == NULL) {
+				perror(mkpath("%s/%s", dotest, patch_no));
+				die("fopen failed");
+			}
+
+			snprintf(msg_path, sizeof(msg_path), "%s/msg", dotest);
+			if (mailinfo(in, out, keep_subject, utf8 ? git_commit_encoding : NULL,
+			    msg_path, mkpath("%s/patch",dotest)))
+				    stop_here(this);
+			fclose(in);
+			fclose(out);
+
+			in = fopen(msg_path, "r");
+			out = fopen(mkpath("%s/msg-clean", dotest), "w");
+			stripspace(in, out);
+			fclose(in);
+			fclose(out);
+		}
+
+		fcat("info", "Author: %1000[^\n]\nEmail: %1000s\n"
+		     "Subject: %992[^\n]\nDate: %1000[^\n]\n\n",
+		     name, email, subject + 8 /*[PATCH] */, date);
+
+		if (!keep_subject)
+			subject = subject + 8; /*[PATCH] */
+
+		if (email == NULL || !strcmp(email, "")) {
+			printf("Patch does not have a valid e-mail address.\n");
+			stop_here(this);
+		}
+
+		if (!resume) { /* Prepare the commit-message and the patch */
+			char c, *t;
+			char line[1000];
+			char last_signoff[1000] = "";
+			FILE *cmt, *msg;
+
+			/* Find the last Signed-off line */
+			msg = fopen(mkpath("%s/msg-clean", dotest), "r");
+			while ((fgets(line, sizeof(line), msg))) {
+				if ((t = strstr(line, "Signed-off-by: ")))
+					strncpy(last_signoff, t, sizeof(last_signoff));
+			}
+			if ((t = strrchr(last_signoff, '>')))
+				*++t = '\0';
+
+			/* Write the commit-mesage */
+			cmt = fopen(mkpath("%s/final-commit", dotest), "w");
+			fprintf(cmt, "%s\n", subject);
+
+			rewind(msg);
+			if ((c = fgetc(msg)) != EOF) {
+				fprintf(cmt, "\n");
+				ungetc(c, msg);
+			}
+			while (fgets(line, sizeof(line), msg))
+				fputs(line, cmt);
+
+			/* Add a signoff */
+			if (signoff && strcmp(last_signoff, sign)) {
+				if (!strcmp(last_signoff, ""))
+					fputc('\n', cmt);
+				fputs(sign, cmt);
+			}
+			fclose(cmt);
+			fclose(msg);
+		} else
+			if (resolved && interactive)
+				/* This is used only for interactive view option. */
+				system(mkpath("git-diff-index -p --cached HEAD >\"%s/patch\"", dotest));
+
+		resume = 0;
+		if (interactive)
+			action = go_interactive();
+		else
+			action = YES;
+
+		if (action == SKIP) {
+			this = go_next(this);
+			continue;
+		}
+
+		if (!access(mkpath("%s/hooks/applypatch-msg", get_git_dir()), X_OK))
+			if (system(mkpath("%s/hooks/applypatch-msg %s/final-commit", get_git_dir(), dotest)))
+				stop_here(this);
+
+		printf("\nApplying %s\n\n", subject);
+
+		if (!resolved) {
+			/*git-apply $binary --index $ws "$dotest/patch" */
+			char patch[PATH_MAX];
+			char *opts[6] = { "git-apply", "--allow-binary-replacement", "--index", NULL, NULL, NULL };
+			char **opt = &opts[0];
+			int optc = 5;
+
+			if (!binary) {
+				opts[1] = "git-apply";
+				opt++; optc--;
+			}
+			opts[3] = whitespace;
+			snprintf(patch, sizeof(patch), "%s/patch", dotest);
+			opts[4] = patch;
+			apply_status = cmd_apply(optc, (const char**)opt, envp);
+		} else {
+			/* Resolved means the user did all the hard work, and
+			   we do not have to do any patch application.  Just
+			   trust what the user has in the index file and the
+			   working tree.*/
+			resolved = 0;
+
+			if (has_zero_output("git-diff-index --cached --name-only HEAD")) {
+				printf("No changes - did you forget update-index?\n");
+				stop_here_user_resolve(this);
+			}
+			if (!has_zero_output("git-ls-files -u")) {
+				printf("You still have unmerged paths in your index,\n"
+					"did you forget update-index?");
+				stop_here_user_resolve(this);
+			}
+			apply_status = 0;
+		}
+
+		if (apply_status && threeway) {
+			fall_back_3way();
+			/* Applying the patch to an earlier tree and merging the
+			   result may have produced the same tree as ours. */
+			if (has_zero_output("git-diff-index --cached --name-only HEAD")) {
+				printf("No changes -- Patch already applied.\n");
+				this = go_next(this);
+				continue;
+			}
+			/* We have merged successfully */
+			apply_status = 0;
+		}
+
+		if (apply_status) {
+			printf("Patch failed at %s\n.", patch_no);
+			stop_here_user_resolve(this);
+		}
+
+		if (!access(mkpath("%s/hooks/pre-applypatch", get_git_dir()), X_OK))
+			if (system(mkpath("%s/hooks/pre-applypatch", get_git_dir())))
+				stop_here(this);
+
+		if (commit(subject) == -1)
+			stop_here(this);
+
+		if (!access(mkpath("%s/hooks/post-applypatch", get_git_dir()), X_OK))
+			system(mkpath("%s/hooks/post-applypatch", get_git_dir()));
+
+		this = go_next(this);
+	}
+	rm_rf(dotest);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index c1f3395..8771e36 100644
--- a/builtin.h
+++ b/builtin.h
@@ -49,6 +49,7 @@ extern int cmd_cat_file(int argc, const 
 extern int cmd_rev_parse(int argc, const char **argv, char **envp);
 extern int cmd_update_index(int argc, const char **argv, char **envp);
 extern int cmd_update_ref(int argc, const char **argv, char **envp);
+extern int cmd_am(int argc, const char **argv, char **envp);
 
 extern int cmd_write_tree(int argc, const char **argv, char **envp);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
diff --git a/git-am.sh b/git-am.sh
deleted file mode 100755
index 4232e27..0000000
--- a/git-am.sh
+++ /dev/null
@@ -1,427 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, 2006 Junio C Hamano
-
-USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
-  [--interactive] [--whitespace=<option>] <mbox>...
-  or, when resuming [--skip | --resolved]'
-. git-sh-setup
-
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-stop_here () {
-    echo "$1" >"$dotest/next"
-    exit 1
-}
-
-stop_here_user_resolve () {
-    if [ -n "$resolvemsg" ]; then
-	    echo "$resolvemsg"
-	    stop_here $1
-    fi
-    cmdline=$(basename $0)
-    if test '' != "$interactive"
-    then
-        cmdline="$cmdline -i"
-    fi
-    if test '' != "$threeway"
-    then
-        cmdline="$cmdline -3"
-    fi
-    if test '.dotest' != "$dotest"
-    then
-        cmdline="$cmdline -d=$dotest"
-    fi
-    echo "When you have resolved this problem run \"$cmdline --resolved\"."
-    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
-
-    stop_here $1
-}
-
-go_next () {
-	rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
-		"$dotest/patch" "$dotest/info"
-	echo "$next" >"$dotest/next"
-	this=$next
-}
-
-fall_back_3way () {
-    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
-
-    rm -fr "$dotest"/patch-merge-*
-    mkdir "$dotest/patch-merge-tmp-dir"
-
-    # First see if the patch records the index info that we can use.
-    if git-apply -z --index-info "$dotest/patch" \
-	>"$dotest/patch-merge-index-info" 2>/dev/null &&
-	GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-	git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
-	GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-	git-write-tree >"$dotest/patch-merge-base+" &&
-	# index has the base tree now.
-	GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-	git-apply $binary --cached <"$dotest/patch"
-    then
-	echo Using index info to reconstruct a base tree...
-	mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
-	mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
-    fi
-
-    test -f "$dotest/patch-merge-index" &&
-    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
-    orig_tree=$(cat "$dotest/patch-merge-base") &&
-    rm -fr "$dotest"/patch-merge-* || exit 1
-
-    echo Falling back to patching base and 3-way merge...
-
-    # This is not so wrong.  Depending on which base we picked,
-    # orig_tree may be wildly different from ours, but his_tree
-    # has the same set of wildly different changes in parts the
-    # patch did not touch, so resolve ends up cancelling them,
-    # saying that we reverted all those changes.
-
-    git-merge-resolve $orig_tree -- HEAD $his_tree || {
-	    if test -d "$GIT_DIR/rr-cache"
-	    then
-		git-rerere
-	    fi
-	    echo Failed to merge in the changes.
-	    exit 1
-    }
-}
-
-prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
-
-while case "$#" in 0) break;; esac
-do
-	case "$1" in
-	-d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
-	dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
-	-d|--d|--do|--dot|--dote|--dotes|--dotest)
-	case "$#" in 1) usage ;; esac; shift
-	dotest="$1"; shift;;
-
-	-i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
-	--interacti|--interactiv|--interactive)
-	interactive=t; shift ;;
-
-	-b|--b|--bi|--bin|--bina|--binar|--binary)
-	binary=t; shift ;;
-
-	-3|--3|--3w|--3wa|--3way)
-	threeway=t; shift ;;
-	-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
-	sign=t; shift ;;
-	-u|--u|--ut|--utf|--utf8)
-	utf8=t; shift ;;
-	-k|--k|--ke|--kee|--keep)
-	keep=t; shift ;;
-
-	-r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
-	resolved=t; shift ;;
-
-	--sk|--ski|--skip)
-	skip=t; shift ;;
-
-	--whitespace=*)
-	ws=$1; shift ;;
-
-	--resolvemsg=*)
-	resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
-
-	--)
-	shift; break ;;
-	-*)
-	usage ;;
-	*)
-	break ;;
-	esac
-done
-
-# If the dotest directory exists, but we have finished applying all the
-# patches in them, clear it out.
-if test -d "$dotest" &&
-   last=$(cat "$dotest/last") &&
-   next=$(cat "$dotest/next") &&
-   test $# != 0 &&
-   test "$next" -gt "$last"
-then
-   rm -fr "$dotest"
-fi
-
-if test -d "$dotest"
-then
-	test ",$#," = ",0," ||
-	die "previous dotest directory $dotest still exists but mbox given."
-	resume=yes
-else
-	# Make sure we are not given --skip nor --resolved
-	test ",$skip,$resolved," = ,,, ||
-		die "Resolve operation not in progress, we are not resuming."
-
-	# Start afresh.
-	mkdir -p "$dotest" || exit
-
-	git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
-		rm -fr "$dotest"
-		exit 1
-	}
-
-	# -b, -s, -u, -k and --whitespace flags are kept for the
-	# resuming session after a patch failure.
-	# -3 and -i can and must be given when resuming.
-	echo "$binary" >"$dotest/binary"
-	echo " $ws" >"$dotest/whitespace"
-	echo "$sign" >"$dotest/sign"
-	echo "$utf8" >"$dotest/utf8"
-	echo "$keep" >"$dotest/keep"
-	echo 1 >"$dotest/next"
-fi
-
-case "$resolved" in
-'')
-	files=$(git-diff-index --cached --name-only HEAD) || exit
-	if [ "$files" ]; then
-	   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
-	   exit 1
-	fi
-esac
-
-if test "$(cat "$dotest/binary")" = t
-then
-	binary=--allow-binary-replacement
-fi
-if test "$(cat "$dotest/utf8")" = t
-then
-	utf8=-u
-fi
-if test "$(cat "$dotest/keep")" = t
-then
-	keep=-k
-fi
-ws=`cat "$dotest/whitespace"`
-if test "$(cat "$dotest/sign")" = t
-then
-	SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
-			s/>.*/>/
-			s/^/Signed-off-by: /'
-		`
-else
-	SIGNOFF=
-fi
-
-last=`cat "$dotest/last"`
-this=`cat "$dotest/next"`
-if test "$skip" = t
-then
-	this=`expr "$this" + 1`
-	resume=
-fi
-
-if test "$this" -gt "$last"
-then
-	echo Nothing to do.
-	rm -fr "$dotest"
-	exit
-fi
-
-while test "$this" -le "$last"
-do
-	msgnum=`printf "%0${prec}d" $this`
-	next=`expr "$this" + 1`
-	test -f "$dotest/$msgnum" || {
-		resume=
-		go_next
-		continue
-	}
-
-	# If we are not resuming, parse and extract the patch information
-	# into separate files:
-	#  - info records the authorship and title
-	#  - msg is the rest of commit log message
-	#  - patch is the patch body.
-	#
-	# When we are resuming, these files are either already prepared
-	# by the user, or the user can tell us to do so by --resolved flag.
-	case "$resume" in
-	'')
-		git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
-			<"$dotest/$msgnum" >"$dotest/info" ||
-			stop_here $this
-		git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
-		;;
-	esac
-
-	GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
-	GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
-	GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
-
-	if test -z "$GIT_AUTHOR_EMAIL"
-	then
-		echo "Patch does not have a valid e-mail address."
-		stop_here $this
-	fi
-
-	export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-
-	SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
-	case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
-
-	case "$resume" in
-	'')
-	    if test '' != "$SIGNOFF"
-	    then
-		LAST_SIGNED_OFF_BY=`
-		    sed -ne '/^Signed-off-by: /p' \
-		    "$dotest/msg-clean" |
-		    tail -n 1
-		`
-		ADD_SIGNOFF=`
-		    test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
-		    test '' = "$LAST_SIGNED_OFF_BY" && echo
-		    echo "$SIGNOFF"
-		}`
-	    else
-		ADD_SIGNOFF=
-	    fi
-	    {
-		echo "$SUBJECT"
-		if test -s "$dotest/msg-clean"
-		then
-			echo
-			cat "$dotest/msg-clean"
-		fi
-		if test '' != "$ADD_SIGNOFF"
-		then
-			echo "$ADD_SIGNOFF"
-		fi
-	    } >"$dotest/final-commit"
-	    ;;
-	*)
-		case "$resolved$interactive" in
-		tt)
-			# This is used only for interactive view option.
-			git-diff-index -p --cached HEAD >"$dotest/patch"
-			;;
-		esac
-	esac
-
-	resume=
-	if test "$interactive" = t
-	then
-	    test -t 0 ||
-	    die "cannot be interactive without stdin connected to a terminal."
-	    action=again
-	    while test "$action" = again
-	    do
-		echo "Commit Body is:"
-		echo "--------------------------"
-		cat "$dotest/final-commit"
-		echo "--------------------------"
-		printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-		read reply
-		case "$reply" in
-		[yY]*) action=yes ;;
-		[aA]*) action=yes interactive= ;;
-		[nN]*) action=skip ;;
-		[eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
-		       action=again ;;
-		[vV]*) action=again
-		       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
-		*)     action=again ;;
-		esac
-	    done
-	else
-	    action=yes
-	fi
-
-	if test $action = skip
-	then
-		go_next
-		continue
-	fi
-
-	if test -x "$GIT_DIR"/hooks/applypatch-msg
-	then
-		"$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
-		stop_here $this
-	fi
-
-	echo
-	echo "Applying '$SUBJECT'"
-	echo
-
-	case "$resolved" in
-	'')
-		git-apply $binary --index $ws "$dotest/patch"
-		apply_status=$?
-		;;
-	t)
-		# Resolved means the user did all the hard work, and
-		# we do not have to do any patch application.  Just
-		# trust what the user has in the index file and the
-		# working tree.
-		resolved=
-		changed="$(git-diff-index --cached --name-only HEAD)"
-		if test '' = "$changed"
-		then
-			echo "No changes - did you forget update-index?"
-			stop_here_user_resolve $this
-		fi
-		unmerged=$(git-ls-files -u)
-		if test -n "$unmerged"
-		then
-			echo "You still have unmerged paths in your index"
-			echo "did you forget update-index?"
-			stop_here_user_resolve $this
-		fi
-		apply_status=0
-		;;
-	esac
-
-	if test $apply_status = 1 && test "$threeway" = t
-	then
-		if (fall_back_3way)
-		then
-		    # Applying the patch to an earlier tree and merging the
-		    # result may have produced the same tree as ours.
-		    changed="$(git-diff-index --cached --name-only HEAD)"
-		    if test '' = "$changed"
-		    then
-			    echo No changes -- Patch already applied.
-			    go_next
-			    continue
-		    fi
-		    # clear apply_status -- we have successfully merged.
-		    apply_status=0
-		fi
-	fi
-	if test $apply_status != 0
-	then
-		echo Patch failed at $msgnum.
-		stop_here_user_resolve $this
-	fi
-
-	if test -x "$GIT_DIR"/hooks/pre-applypatch
-	then
-		"$GIT_DIR"/hooks/pre-applypatch || stop_here $this
-	fi
-
-	tree=$(git-write-tree) &&
-	echo Wrote tree $tree &&
-	parent=$(git-rev-parse --verify HEAD) &&
-	commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
-	echo Committed: $commit &&
-	git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
-	stop_here $this
-
-	if test -x "$GIT_DIR"/hooks/post-applypatch
-	then
-		"$GIT_DIR"/hooks/post-applypatch
-	fi
-
-	go_next
-done
-
-rm -fr "$dotest"
diff --git a/git.c b/git.c
index 652e3c4..b9261e4 100644
--- a/git.c
+++ b/git.c
@@ -184,7 +184,8 @@ static void handle_internal_command(int 
 		{ "mailinfo", cmd_mailinfo },
 		{ "stripspace", cmd_stripspace },
 		{ "update-index", cmd_update_index },
-		{ "update-ref", cmd_update_ref }
+		{ "update-ref", cmd_update_ref },
+		{ "am", cmd_am }
 	};
 	int i;
 
-- 
1.4.0



-
: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]