From: "Lukas Sandström" <lukass@etek.chalmers.se>
To: Junio C Hamano <junkio@cox.net>, Git Mailing List <git@vger.kernel.org>
Cc: "Lukas Sandström" <lukass@etek.chalmers.se>
Subject: [PATCH/RFC 8/8] Make git-am a builtin
Date: Tue, 13 Jun 2006 22:22:06 +0200 [thread overview]
Message-ID: <448F1E6E.8000003@etek.chalmers.se> (raw)
In-Reply-To: <448EF791.7070504@etek.chalmers.se>
Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
---
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
prev parent reply other threads:[~2006-06-13 20:22 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <448EF791.7070504@etek.chalmers.se>
2006-06-13 20:21 ` [PATCH 1/8] Make git-write-tree a builtin Lukas Sandström
2006-06-13 20:21 ` [PATCH 2/8] Make git-mailsplit " Lukas Sandström
2006-06-13 20:21 ` [PATCH 3/8] Make git-mailinfo " Lukas Sandström
2006-06-13 20:21 ` [PATCH 4/8] Make git-stripspace " Lukas Sandström
2006-06-13 20:21 ` [PATCH 5/8] Make git-update-index " Lukas Sandström
2006-06-13 20:22 ` [PATCH 6/8] Make git-update-ref " Lukas Sandström
2006-06-14 2:22 ` Shawn Pearce
2006-06-13 20:22 ` [PATCH 7/8] Make it possible to call cmd_apply multiple times Lukas Sandström
2006-06-13 20:22 ` Lukas Sandström [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=448F1E6E.8000003@etek.chalmers.se \
--to=lukass@etek.chalmers.se \
--cc=git@vger.kernel.org \
--cc=junkio@cox.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).