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