From 3ff53df7b4cbc331d302181d2d6644f4cb860a52 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:22 +0800 Subject: wrapper: implement xopen() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A common usage pattern of open() is to check if it was successful, and die() if it was not: int fd = open(path, O_WRONLY | O_CREAT, 0777); if (fd < 0) die_errno(_("Could not open '%s' for writing."), path); Implement a wrapper function xopen() that does the above so that we can save a few lines of code, and make the die() messages consistent. Helped-by: Torsten Bögershausen Helped-by: Jeff King Helped-by: Johannes Schindelin Helped-by: Junio C Hamano Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- git-compat-util.h | 1 + wrapper.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index c6d391f864..e168dfd68b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -717,6 +717,7 @@ extern void *xrealloc(void *ptr, size_t size); extern void *xcalloc(size_t nmemb, size_t size); extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); extern void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset); +extern int xopen(const char *path, int flags, ...); extern ssize_t xread(int fd, void *buf, size_t len); extern ssize_t xwrite(int fd, const void *buf, size_t len); extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset); diff --git a/wrapper.c b/wrapper.c index ff49807948..0a4502d232 100644 --- a/wrapper.c +++ b/wrapper.c @@ -189,6 +189,41 @@ void *xcalloc(size_t nmemb, size_t size) # endif #endif +/** + * xopen() is the same as open(), but it die()s if the open() fails. + */ +int xopen(const char *path, int oflag, ...) +{ + mode_t mode = 0; + va_list ap; + + /* + * va_arg() will have undefined behavior if the specified type is not + * compatible with the argument type. Since integers are promoted to + * ints, we fetch the next argument as an int, and then cast it to a + * mode_t to avoid undefined behavior. + */ + va_start(ap, oflag); + if (oflag & O_CREAT) + mode = va_arg(ap, int); + va_end(ap); + + for (;;) { + int fd = open(path, oflag, mode); + if (fd >= 0) + return fd; + if (errno == EINTR) + continue; + + if ((oflag & O_RDWR) == O_RDWR) + die_errno(_("could not open '%s' for reading and writing"), path); + else if ((oflag & O_WRONLY) == O_WRONLY) + die_errno(_("could not open '%s' for writing"), path); + else + die_errno(_("could not open '%s' for reading"), path); + } +} + /* * xread() is the same a read(), but it automatically restarts read() * operations with a recoverable error (EAGAIN and EINTR). xread() -- cgit v1.2.3 From 260eec292736388831958637eccdcf1a8f00e14d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:23 +0800 Subject: wrapper: implement xfopen() A common usage pattern of fopen() is to check if it succeeded, and die() if it failed: FILE *fp = fopen(path, "w"); if (!fp) die_errno(_("could not open '%s' for writing"), path); Implement a wrapper function xfopen() for the above, so that we can save a few lines of code and make the die() messages consistent. Helped-by: Jeff King Helped-by: Johannes Schindelin Helped-by: Junio C Hamano Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- git-compat-util.h | 1 + wrapper.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index e168dfd68b..392da79029 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -722,6 +722,7 @@ extern ssize_t xread(int fd, void *buf, size_t len); extern ssize_t xwrite(int fd, const void *buf, size_t len); extern ssize_t xpread(int fd, void *buf, size_t len, off_t offset); extern int xdup(int fd); +extern FILE *xfopen(const char *path, const char *mode); extern FILE *xfdopen(int fd, const char *mode); extern int xmkstemp(char *template); extern int xmkstemp_mode(char *template, int mode); diff --git a/wrapper.c b/wrapper.c index 0a4502d232..e451463431 100644 --- a/wrapper.c +++ b/wrapper.c @@ -346,6 +346,27 @@ int xdup(int fd) return ret; } +/** + * xfopen() is the same as fopen(), but it die()s if the fopen() fails. + */ +FILE *xfopen(const char *path, const char *mode) +{ + for (;;) { + FILE *fp = fopen(path, mode); + if (fp) + return fp; + if (errno == EINTR) + continue; + + if (*mode && mode[1] == '+') + die_errno(_("could not open '%s' for reading and writing"), path); + else if (*mode == 'w' || *mode == 'a') + die_errno(_("could not open '%s' for writing"), path); + else + die_errno(_("could not open '%s' for reading"), path); + } +} + FILE *xfdopen(int fd, const char *mode) { FILE *stream = fdopen(fd, mode); -- cgit v1.2.3 From 73c2779f421fe1eaead6f1c07a0e134a9c17d6db Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:24 +0800 Subject: builtin-am: implement skeletal builtin am For the purpose of rewriting git-am.sh into a C builtin, implement a skeletal builtin/am.c that redirects to $GIT_EXEC_PATH/git-am if the environment variable _GIT_USE_BUILTIN_AM is not defined. Since in the Makefile git-am.sh takes precedence over builtin/am.c, $GIT_EXEC_PATH/git-am will contain the shell script git-am.sh, and thus this allows us to fall back on the functional git-am.sh when running the test suite for tests that depend on a working git-am implementation. Since git-am.sh cannot handle any environment modifications by setup_git_directory(), "am" is declared with no setup flags in git.c. On the other hand, to re-implement git-am.sh in builtin/am.c, we need to run all the git dir and work tree setup logic that git.c typically does for us. As such, we work around this temporarily by copying the logic in git.c's run_builtin(), which is roughly: prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); This redirection should be removed when all the features of git-am.sh have been re-implemented in builtin/am.c. Helped-by: Junio C Hamano Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin.h | 1 + builtin/am.c | 29 +++++++++++++++++++++++++++++ git.c | 6 ++++++ 4 files changed, 37 insertions(+) create mode 100644 builtin/am.c diff --git a/Makefile b/Makefile index 7efedbe834..da451f8bca 100644 --- a/Makefile +++ b/Makefile @@ -813,6 +813,7 @@ LIB_OBJS += xdiff-interface.o LIB_OBJS += zlib.o BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/am.o BUILTIN_OBJS += builtin/annotate.o BUILTIN_OBJS += builtin/apply.o BUILTIN_OBJS += builtin/archive.o diff --git a/builtin.h b/builtin.h index 839483de6e..79aaf0afe8 100644 --- a/builtin.h +++ b/builtin.h @@ -30,6 +30,7 @@ extern int textconv_object(const char *path, unsigned mode, const unsigned char extern int is_builtin(const char *s); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_am(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c new file mode 100644 index 0000000000..fd32caf107 --- /dev/null +++ b/builtin/am.c @@ -0,0 +1,29 @@ +/* + * Builtin "git am" + * + * Based on git-am.sh by Junio C Hamano. + */ +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" + +int cmd_am(int argc, const char **argv, const char *prefix) +{ + + /* + * NEEDSWORK: Once all the features of git-am.sh have been + * re-implemented in builtin/am.c, this preamble can be removed. + */ + if (!getenv("_GIT_USE_BUILTIN_AM")) { + const char *path = mkpath("%s/git-am", git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + } else { + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + } + + return 0; +} diff --git a/git.c b/git.c index 55c327c7b3..38d9ad531e 100644 --- a/git.c +++ b/git.c @@ -370,6 +370,12 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) static struct cmd_struct commands[] = { { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, + /* + * NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has + * been removed, this entry should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "am", cmd_am }, { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, { "archive", cmd_archive }, -- cgit v1.2.3 From 8c3bd9e288b3ce70d02d8a843219168a9589e917 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:25 +0800 Subject: builtin-am: implement patch queue mechanism git-am applies a series of patches. If the process terminates abnormally, we want to be able to resume applying the series of patches. This requires the session state to be saved in a persistent location. Implement the mechanism of a "patch queue", represented by 2 integers -- the index of the current patch we are applying and the index of the last patch, as well as its lifecycle through the following functions: * am_setup(), which will set up the state directory $GIT_DIR/rebase-apply. As such, even if the process exits abnormally, the last-known state will still persist. * am_load(), which is called if there is an am session in progress, to load the last known state from the state directory so we can resume applying patches. * am_run(), which will do the actual patch application. After applying a patch, it calls am_next() to increment the current patch index. The logic for applying and committing a patch is not implemented yet. * am_destroy(), which is finally called when we successfully applied all the patches in the queue, to clean up by removing the state directory and its contents. Helped-by: Junio C Hamano Helped-by: Stefan Beller Helped-by: Johannes Schindelin Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index fd32caf107..ac172c4841 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -6,9 +6,171 @@ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" +#include "parse-options.h" +#include "dir.h" + +struct am_state { + /* state directory path */ + char *dir; + + /* current and last patch numbers, 1-indexed */ + int cur; + int last; +}; + +/** + * Initializes am_state with the default values. The state directory is set to + * dir. + */ +static void am_state_init(struct am_state *state, const char *dir) +{ + memset(state, 0, sizeof(*state)); + + assert(dir); + state->dir = xstrdup(dir); +} + +/** + * Releases memory allocated by an am_state. + */ +static void am_state_release(struct am_state *state) +{ + free(state->dir); +} + +/** + * Returns path relative to the am_state directory. + */ +static inline const char *am_path(const struct am_state *state, const char *path) +{ + return mkpath("%s/%s", state->dir, path); +} + +/** + * Returns 1 if there is an am session in progress, 0 otherwise. + */ +static int am_in_progress(const struct am_state *state) +{ + struct stat st; + + if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode)) + return 0; + if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode)) + return 0; + if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode)) + return 0; + return 1; +} + +/** + * Reads the contents of `file` in the `state` directory into `sb`. Returns the + * number of bytes read on success, -1 if the file does not exist. If `trim` is + * set, trailing whitespace will be removed. + */ +static int read_state_file(struct strbuf *sb, const struct am_state *state, + const char *file, int trim) +{ + strbuf_reset(sb); + + if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) { + if (trim) + strbuf_trim(sb); + + return sb->len; + } + + if (errno == ENOENT) + return -1; + + die_errno(_("could not read '%s'"), am_path(state, file)); +} + +/** + * Loads state from disk. + */ +static void am_load(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + if (read_state_file(&sb, state, "next", 1) < 0) + die("BUG: state file 'next' does not exist"); + state->cur = strtol(sb.buf, NULL, 10); + + if (read_state_file(&sb, state, "last", 1) < 0) + die("BUG: state file 'last' does not exist"); + state->last = strtol(sb.buf, NULL, 10); + + strbuf_release(&sb); +} + +/** + * Removes the am_state directory, forcefully terminating the current am + * session. + */ +static void am_destroy(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, state->dir); + remove_dir_recursively(&sb, 0); + strbuf_release(&sb); +} + +/** + * Setup a new am session for applying patches + */ +static void am_setup(struct am_state *state) +{ + if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) + die_errno(_("failed to create directory '%s'"), state->dir); + + /* + * NOTE: Since the "next" and "last" files determine if an am_state + * session is in progress, they should be written last. + */ + + write_file(am_path(state, "next"), 1, "%d", state->cur); + + write_file(am_path(state, "last"), 1, "%d", state->last); +} + +/** + * Increments the patch pointer, and cleans am_state for the application of the + * next patch. + */ +static void am_next(struct am_state *state) +{ + state->cur++; + write_file(am_path(state, "next"), 1, "%d", state->cur); +} + +/** + * Applies all queued mail. + */ +static void am_run(struct am_state *state) +{ + while (state->cur <= state->last) { + + /* NEEDSWORK: Patch application not implemented yet */ + + am_next(state); + } + + am_destroy(state); +} int cmd_am(int argc, const char **argv, const char *prefix) { + struct am_state state; + + const char * const usage[] = { + N_("git am [options] [(|)...]"), + NULL + }; + + struct option options[] = { + OPT_END() + }; /* * NEEDSWORK: Once all the features of git-am.sh have been @@ -25,5 +187,20 @@ int cmd_am(int argc, const char **argv, const char *prefix) setup_work_tree(); } + git_config(git_default_config, NULL); + + am_state_init(&state, git_path("rebase-apply")); + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (am_in_progress(&state)) + am_load(&state); + else + am_setup(&state); + + am_run(&state); + + am_state_release(&state); + return 0; } -- cgit v1.2.3 From 11c2177f2c264513f8d3abe64666fa05bcd84a57 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:26 +0800 Subject: builtin-am: split out mbox/maildir patches with git-mailsplit git-am.sh supports mbox, stgit and mercurial patches. Re-implement support for splitting out mbox/maildirs using git-mailsplit, while also implementing the framework required to support other patch formats in the future. Re-implement support for the --patch-format option (since a5a6755 (git-am foreign patch support: introduce patch_format, 2009-05-27)) to allow the user to choose between the different patch formats. Helped-by: Junio C Hamano Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index ac172c4841..5f3c131357 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -8,6 +8,12 @@ #include "exec_cmd.h" #include "parse-options.h" #include "dir.h" +#include "run-command.h" + +enum patch_format { + PATCH_FORMAT_UNKNOWN = 0, + PATCH_FORMAT_MBOX +}; struct am_state { /* state directory path */ @@ -16,6 +22,9 @@ struct am_state { /* current and last patch numbers, 1-indexed */ int cur; int last; + + /* number of digits in patch filename */ + int prec; }; /** @@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char *dir) assert(dir); state->dir = xstrdup(dir); + + state->prec = 4; } /** @@ -116,14 +127,72 @@ static void am_destroy(const struct am_state *state) strbuf_release(&sb); } +/** + * Splits out individual email patches from `paths`, where each path is either + * a mbox file or a Maildir. Returns 0 on success, -1 on failure. + */ +static int split_mail_mbox(struct am_state *state, const char **paths) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf last = STRBUF_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "mailsplit"); + argv_array_pushf(&cp.args, "-d%d", state->prec); + argv_array_pushf(&cp.args, "-o%s", state->dir); + argv_array_push(&cp.args, "-b"); + argv_array_push(&cp.args, "--"); + argv_array_pushv(&cp.args, paths); + + if (capture_command(&cp, &last, 8)) + return -1; + + state->cur = 1; + state->last = strtol(last.buf, NULL, 10); + + return 0; +} + +/** + * Splits a list of files/directories into individual email patches. Each path + * in `paths` must be a file/directory that is formatted according to + * `patch_format`. + * + * Once split out, the individual email patches will be stored in the state + * directory, with each patch's filename being its index, padded to state->prec + * digits. + * + * state->cur will be set to the index of the first mail, and state->last will + * be set to the index of the last mail. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail(struct am_state *state, enum patch_format patch_format, + const char **paths) +{ + switch (patch_format) { + case PATCH_FORMAT_MBOX: + return split_mail_mbox(state, paths); + default: + die("BUG: invalid patch_format"); + } + return -1; +} + /** * Setup a new am session for applying patches */ -static void am_setup(struct am_state *state) +static void am_setup(struct am_state *state, enum patch_format patch_format, + const char **paths) { if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir); + if (split_mail(state, patch_format, paths) < 0) { + am_destroy(state); + die(_("Failed to split patches.")); + } + /* * NOTE: Since the "next" and "last" files determine if an am_state * session is in progress, they should be written last. @@ -159,9 +228,25 @@ static void am_run(struct am_state *state) am_destroy(state); } +/** + * parse_options() callback that validates and sets opt->value to the + * PATCH_FORMAT_* enum value corresponding to `arg`. + */ +static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset) +{ + int *opt_value = opt->value; + + if (!strcmp(arg, "mbox")) + *opt_value = PATCH_FORMAT_MBOX; + else + return error(_("Invalid value for --patch-format: %s"), arg); + return 0; +} + int cmd_am(int argc, const char **argv, const char *prefix) { struct am_state state; + int patch_format = PATCH_FORMAT_UNKNOWN; const char * const usage[] = { N_("git am [options] [(|)...]"), @@ -169,6 +254,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) }; struct option options[] = { + OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), + N_("format the patch(es) are in"), + parse_opt_patchformat), OPT_END() }; @@ -195,8 +283,21 @@ int cmd_am(int argc, const char **argv, const char *prefix) if (am_in_progress(&state)) am_load(&state); - else - am_setup(&state); + else { + struct argv_array paths = ARGV_ARRAY_INIT; + int i; + + for (i = 0; i < argc; i++) { + if (is_absolute_path(argv[i]) || !prefix) + argv_array_push(&paths, argv[i]); + else + argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i])); + } + + am_setup(&state, patch_format, paths.argv); + + argv_array_clear(&paths); + } am_run(&state); -- cgit v1.2.3 From c29807b27dc60e17eb1cd8bc50900af2a09ce66f Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:27 +0800 Subject: builtin-am: auto-detect mbox patches Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and mercurial patches through heuristics. Re-implement support for autodetecting mbox/maildir files in builtin/am.c. RFC 2822 requires that lines are terminated by "\r\n". To support this, implement strbuf_getline_crlf(), which will remove both '\n' and "\r\n" from the end of the line. Helped-by: Junio C Hamano Helped-by: Eric Sunshine Helped-by: Johannes Schindelin Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 5f3c131357..c12566ab4a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -10,6 +10,21 @@ #include "dir.h" #include "run-command.h" +/** + * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators. + */ +static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp) +{ + if (strbuf_getwholeline(sb, fp, '\n')) + return EOF; + if (sb->buf[sb->len - 1] == '\n') { + strbuf_setlen(sb, sb->len - 1); + if (sb->len > 0 && sb->buf[sb->len - 1] == '\r') + strbuf_setlen(sb, sb->len - 1); + } + return 0; +} + enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX @@ -127,6 +142,92 @@ static void am_destroy(const struct am_state *state) strbuf_release(&sb); } +/** + * Determines if the file looks like a piece of RFC2822 mail by grabbing all + * non-indented lines and checking if they look like they begin with valid + * header field names. + * + * Returns 1 if the file looks like a piece of mail, 0 otherwise. + */ +static int is_mail(FILE *fp) +{ + const char *header_regex = "^[!-9;-~]+:"; + struct strbuf sb = STRBUF_INIT; + regex_t regex; + int ret = 1; + + if (fseek(fp, 0L, SEEK_SET)) + die_errno(_("fseek failed")); + + if (regcomp(®ex, header_regex, REG_NOSUB | REG_EXTENDED)) + die("invalid pattern: %s", header_regex); + + while (!strbuf_getline_crlf(&sb, fp)) { + if (!sb.len) + break; /* End of header */ + + /* Ignore indented folded lines */ + if (*sb.buf == '\t' || *sb.buf == ' ') + continue; + + /* It's a header if it matches header_regex */ + if (regexec(®ex, sb.buf, 0, NULL, 0)) { + ret = 0; + goto done; + } + } + +done: + regfree(®ex); + strbuf_release(&sb); + return ret; +} + +/** + * Attempts to detect the patch_format of the patches contained in `paths`, + * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if + * detection fails. + */ +static int detect_patch_format(const char **paths) +{ + enum patch_format ret = PATCH_FORMAT_UNKNOWN; + struct strbuf l1 = STRBUF_INIT; + FILE *fp; + + /* + * We default to mbox format if input is from stdin and for directories + */ + if (!*paths || !strcmp(*paths, "-") || is_directory(*paths)) + return PATCH_FORMAT_MBOX; + + /* + * Otherwise, check the first few lines of the first patch, starting + * from the first non-blank line, to try to detect its format. + */ + + fp = xfopen(*paths, "r"); + + while (!strbuf_getline_crlf(&l1, fp)) { + if (l1.len) + break; + } + + if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) { + ret = PATCH_FORMAT_MBOX; + goto done; + } + + if (l1.len && is_mail(fp)) { + ret = PATCH_FORMAT_MBOX; + goto done; + } + +done: + fclose(fp); + strbuf_release(&l1); + return ret; +} + /** * Splits out individual email patches from `paths`, where each path is either * a mbox file or a Maildir. Returns 0 on success, -1 on failure. @@ -185,6 +286,14 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, static void am_setup(struct am_state *state, enum patch_format patch_format, const char **paths) { + if (!patch_format) + patch_format = detect_patch_format(paths); + + if (!patch_format) { + fprintf_ln(stderr, _("Patch format detection failed.")); + exit(128); + } + if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir); -- cgit v1.2.3 From 3e20dcf367f551e5a0efab74e7bc2c07f0549a3d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:28 +0800 Subject: builtin-am: extract patch and commit info with git-mailinfo For the purpose of applying the patch and committing the results, implement extracting the patch data, commit message and authorship from an e-mail message using git-mailinfo. git-mailinfo is run as a separate process, but ideally in the future, we should be be able to access its functionality directly without spawning a new process. Helped-by: Junio C Hamano Helped-by: Jeff King Helped-by: Johannes Schindelin Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index c12566ab4a..63f0fa4dcf 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -9,6 +9,23 @@ #include "parse-options.h" #include "dir.h" #include "run-command.h" +#include "quote.h" + +/** + * Returns 1 if the file is empty or does not exist, 0 otherwise. + */ +static int is_empty_file(const char *filename) +{ + struct stat st; + + if (stat(filename, &st) < 0) { + if (errno == ENOENT) + return 1; + die_errno(_("could not stat %s"), filename); + } + + return !st.st_size; +} /** * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators. @@ -38,6 +55,13 @@ struct am_state { int cur; int last; + /* commit metadata and message */ + char *author_name; + char *author_email; + char *author_date; + char *msg; + size_t msg_len; + /* number of digits in patch filename */ int prec; }; @@ -62,6 +86,10 @@ static void am_state_init(struct am_state *state, const char *dir) static void am_state_release(struct am_state *state) { free(state->dir); + free(state->author_name); + free(state->author_email); + free(state->author_date); + free(state->msg); } /** @@ -111,6 +139,161 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state, die_errno(_("could not read '%s'"), am_path(state, file)); } +/** + * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE + * as a newly-allocated string. VALUE must be a quoted string, and the KEY must + * match `key`. Returns NULL on failure. + * + * This is used by read_author_script() to read the GIT_AUTHOR_* variables from + * the author-script. + */ +static char *read_shell_var(FILE *fp, const char *key) +{ + struct strbuf sb = STRBUF_INIT; + const char *str; + + if (strbuf_getline(&sb, fp, '\n')) + goto fail; + + if (!skip_prefix(sb.buf, key, &str)) + goto fail; + + if (!skip_prefix(str, "=", &str)) + goto fail; + + strbuf_remove(&sb, 0, str - sb.buf); + + str = sq_dequote(sb.buf); + if (!str) + goto fail; + + return strbuf_detach(&sb, NULL); + +fail: + strbuf_release(&sb); + return NULL; +} + +/** + * Reads and parses the state directory's "author-script" file, and sets + * state->author_name, state->author_email and state->author_date accordingly. + * Returns 0 on success, -1 if the file could not be parsed. + * + * The author script is of the format: + * + * GIT_AUTHOR_NAME='$author_name' + * GIT_AUTHOR_EMAIL='$author_email' + * GIT_AUTHOR_DATE='$author_date' + * + * where $author_name, $author_email and $author_date are quoted. We are strict + * with our parsing, as the file was meant to be eval'd in the old git-am.sh + * script, and thus if the file differs from what this function expects, it is + * better to bail out than to do something that the user does not expect. + */ +static int read_author_script(struct am_state *state) +{ + const char *filename = am_path(state, "author-script"); + FILE *fp; + + assert(!state->author_name); + assert(!state->author_email); + assert(!state->author_date); + + fp = fopen(filename, "r"); + if (!fp) { + if (errno == ENOENT) + return 0; + die_errno(_("could not open '%s' for reading"), filename); + } + + state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME"); + if (!state->author_name) { + fclose(fp); + return -1; + } + + state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL"); + if (!state->author_email) { + fclose(fp); + return -1; + } + + state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE"); + if (!state->author_date) { + fclose(fp); + return -1; + } + + if (fgetc(fp) != EOF) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +/** + * Saves state->author_name, state->author_email and state->author_date in the + * state directory's "author-script" file. + */ +static void write_author_script(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, "GIT_AUTHOR_NAME="); + sq_quote_buf(&sb, state->author_name); + strbuf_addch(&sb, '\n'); + + strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL="); + sq_quote_buf(&sb, state->author_email); + strbuf_addch(&sb, '\n'); + + strbuf_addstr(&sb, "GIT_AUTHOR_DATE="); + sq_quote_buf(&sb, state->author_date); + strbuf_addch(&sb, '\n'); + + write_file(am_path(state, "author-script"), 1, "%s", sb.buf); + + strbuf_release(&sb); +} + +/** + * Reads the commit message from the state directory's "final-commit" file, + * setting state->msg to its contents and state->msg_len to the length of its + * contents in bytes. + * + * Returns 0 on success, -1 if the file does not exist. + */ +static int read_commit_msg(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + + assert(!state->msg); + + if (read_state_file(&sb, state, "final-commit", 0) < 0) { + strbuf_release(&sb); + return -1; + } + + state->msg = strbuf_detach(&sb, &state->msg_len); + return 0; +} + +/** + * Saves state->msg in the state directory's "final-commit" file. + */ +static void write_commit_msg(const struct am_state *state) +{ + int fd; + const char *filename = am_path(state, "final-commit"); + + fd = xopen(filename, O_WRONLY | O_CREAT, 0666); + if (write_in_full(fd, state->msg, state->msg_len) < 0) + die_errno(_("could not write to %s"), filename); + close(fd); +} + /** * Loads state from disk. */ @@ -126,6 +309,11 @@ static void am_load(struct am_state *state) die("BUG: state file 'last' does not exist"); state->last = strtol(sb.buf, NULL, 10); + if (read_author_script(state) < 0) + die(_("could not parse author script")); + + read_commit_msg(state); + strbuf_release(&sb); } @@ -318,19 +506,148 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, */ static void am_next(struct am_state *state) { + free(state->author_name); + state->author_name = NULL; + + free(state->author_email); + state->author_email = NULL; + + free(state->author_date); + state->author_date = NULL; + + free(state->msg); + state->msg = NULL; + state->msg_len = 0; + + unlink(am_path(state, "author-script")); + unlink(am_path(state, "final-commit")); + state->cur++; write_file(am_path(state, "next"), 1, "%d", state->cur); } +/** + * Returns the filename of the current patch email. + */ +static const char *msgnum(const struct am_state *state) +{ + static struct strbuf sb = STRBUF_INIT; + + strbuf_reset(&sb); + strbuf_addf(&sb, "%0*d", state->prec, state->cur); + + return sb.buf; +} + +/** + * Parses `mail` using git-mailinfo, extracting its patch and authorship info. + * state->msg will be set to the patch message. state->author_name, + * state->author_email and state->author_date will be set to the patch author's + * name, email and date respectively. The patch body will be written to the + * state directory's "patch" file. + * + * Returns 1 if the patch should be skipped, 0 otherwise. + */ +static int parse_mail(struct am_state *state, const char *mail) +{ + FILE *fp; + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + struct strbuf author_name = STRBUF_INIT; + struct strbuf author_date = STRBUF_INIT; + struct strbuf author_email = STRBUF_INIT; + int ret = 0; + + cp.git_cmd = 1; + cp.in = xopen(mail, O_RDONLY, 0); + cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777); + + argv_array_push(&cp.args, "mailinfo"); + argv_array_push(&cp.args, am_path(state, "msg")); + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp) < 0) + die("could not parse patch"); + + close(cp.in); + close(cp.out); + + /* Extract message and author information */ + fp = xfopen(am_path(state, "info"), "r"); + while (!strbuf_getline(&sb, fp, '\n')) { + const char *x; + + if (skip_prefix(sb.buf, "Subject: ", &x)) { + if (msg.len) + strbuf_addch(&msg, '\n'); + strbuf_addstr(&msg, x); + } else if (skip_prefix(sb.buf, "Author: ", &x)) + strbuf_addstr(&author_name, x); + else if (skip_prefix(sb.buf, "Email: ", &x)) + strbuf_addstr(&author_email, x); + else if (skip_prefix(sb.buf, "Date: ", &x)) + strbuf_addstr(&author_date, x); + } + fclose(fp); + + /* Skip pine's internal folder data */ + if (!strcmp(author_name.buf, "Mail System Internal Data")) { + ret = 1; + goto finish; + } + + if (is_empty_file(am_path(state, "patch"))) { + printf_ln(_("Patch is empty. Was it split wrong?")); + exit(128); + } + + strbuf_addstr(&msg, "\n\n"); + if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0) + die_errno(_("could not read '%s'"), am_path(state, "msg")); + stripspace(&msg, 0); + + assert(!state->author_name); + state->author_name = strbuf_detach(&author_name, NULL); + + assert(!state->author_email); + state->author_email = strbuf_detach(&author_email, NULL); + + assert(!state->author_date); + state->author_date = strbuf_detach(&author_date, NULL); + + assert(!state->msg); + state->msg = strbuf_detach(&msg, &state->msg_len); + +finish: + strbuf_release(&msg); + strbuf_release(&author_date); + strbuf_release(&author_email); + strbuf_release(&author_name); + strbuf_release(&sb); + return ret; +} + /** * Applies all queued mail. */ static void am_run(struct am_state *state) { while (state->cur <= state->last) { + const char *mail = am_path(state, msgnum(state)); + + if (!file_exists(mail)) + goto next; + + if (parse_mail(state, mail)) + goto next; /* mail should be skipped */ + + write_author_script(state); + write_commit_msg(state); /* NEEDSWORK: Patch application not implemented yet */ +next: am_next(state); } -- cgit v1.2.3 From 38a824fe050c4da3f2e0979a94062119080a77a0 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:29 +0800 Subject: builtin-am: apply patch with git-apply Implement applying the patch to the index using git-apply. If a file is unchanged but stat-dirty, git-apply may erroneously fail to apply patches, thinking that they conflict with a dirty working tree. As such, since 2a6f08a (am: refresh the index at start and --resolved, 2011-08-15), git-am will refresh the index before applying patches. Re-implement this behavior. Helped-by: Junio C Hamano Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 63f0fa4dcf..1f198e4f31 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -10,6 +10,7 @@ #include "dir.h" #include "run-command.h" #include "quote.h" +#include "lockfile.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -42,6 +43,14 @@ static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp) return 0; } +/** + * Returns the length of the first line of msg. + */ +static int linelen(const char *msg) +{ + return strchrnul(msg, '\n') - msg; +} + enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX @@ -539,6 +548,19 @@ static const char *msgnum(const struct am_state *state) return sb.buf; } +/** + * Refresh and write index. + */ +static void refresh_and_write_cache(void) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + hold_locked_index(lock_file, 1); + refresh_cache(REFRESH_QUIET); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write index file")); +} + /** * Parses `mail` using git-mailinfo, extracting its patch and authorship info. * state->msg will be set to the patch message. state->author_name, @@ -628,11 +650,36 @@ finish: return ret; } +/** + * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. + */ +static int run_apply(const struct am_state *state) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + + argv_array_push(&cp.args, "apply"); + argv_array_push(&cp.args, "--index"); + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp)) + return -1; + + /* Reload index as git-apply will have modified it. */ + discard_cache(); + read_cache(); + + return 0; +} + /** * Applies all queued mail. */ static void am_run(struct am_state *state) { + refresh_and_write_cache(); + while (state->cur <= state->last) { const char *mail = am_path(state, msgnum(state)); @@ -645,7 +692,27 @@ static void am_run(struct am_state *state) write_author_script(state); write_commit_msg(state); - /* NEEDSWORK: Patch application not implemented yet */ + printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg); + + if (run_apply(state) < 0) { + int advice_amworkdir = 1; + + printf_ln(_("Patch failed at %s %.*s"), msgnum(state), + linelen(state->msg), state->msg); + + git_config_get_bool("advice.amworkdir", &advice_amworkdir); + + if (advice_amworkdir) + printf_ln(_("The copy of the patch that failed is found in: %s"), + am_path(state, "patch")); + + exit(128); + } + + /* + * NEEDSWORK: After the patch has been applied to the index + * with git-apply, we need to make commit as well. + */ next: am_next(state); @@ -707,6 +774,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, usage, 0); + if (read_index_preload(&the_index, NULL) < 0) + die(_("failed to read the index")); + if (am_in_progress(&state)) am_load(&state); else { -- cgit v1.2.3 From c9e8d960b612d5962cd1e952916c2ab6f483e620 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:30 +0800 Subject: builtin-am: implement committing applied patch Implement do_commit(), which commits the index which contains the results of applying the patch, along with the extracted commit message and authorship information. Since 29b6754 (am: remove rebase-apply directory before gc, 2010-02-22), git gc --auto is also invoked to pack the loose objects that are created from making the commits. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 1f198e4f31..a2811b687a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -11,6 +11,9 @@ #include "run-command.h" #include "quote.h" #include "lockfile.h" +#include "cache-tree.h" +#include "refs.h" +#include "commit.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -673,11 +676,57 @@ static int run_apply(const struct am_state *state) return 0; } +/** + * Commits the current index with state->msg as the commit message and + * state->author_name, state->author_email and state->author_date as the author + * information. + */ +static void do_commit(const struct am_state *state) +{ + unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ], + commit[GIT_SHA1_RAWSZ]; + unsigned char *ptr; + struct commit_list *parents = NULL; + const char *reflog_msg, *author; + struct strbuf sb = STRBUF_INIT; + + if (write_cache_as_tree(tree, 0, NULL)) + die(_("git write-tree failed to write a tree")); + + if (!get_sha1_commit("HEAD", parent)) { + ptr = parent; + commit_list_insert(lookup_commit(parent), &parents); + } else { + ptr = NULL; + fprintf_ln(stderr, _("applying to an empty history")); + } + + author = fmt_ident(state->author_name, state->author_email, + state->author_date, IDENT_STRICT); + + if (commit_tree(state->msg, state->msg_len, tree, parents, commit, + author, NULL)) + die(_("failed to write commit object")); + + reflog_msg = getenv("GIT_REFLOG_ACTION"); + if (!reflog_msg) + reflog_msg = "am"; + + strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg), + state->msg); + + update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR); + + strbuf_release(&sb); +} + /** * Applies all queued mail. */ static void am_run(struct am_state *state) { + const char *argv_gc_auto[] = {"gc", "--auto", NULL}; + refresh_and_write_cache(); while (state->cur <= state->last) { @@ -709,16 +758,14 @@ static void am_run(struct am_state *state) exit(128); } - /* - * NEEDSWORK: After the patch has been applied to the index - * with git-apply, we need to make commit as well. - */ + do_commit(state); next: am_next(state); } am_destroy(state); + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); } /** -- cgit v1.2.3 From 32a5fcbfe9e9436224278ec7eb137b0f2905928d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:31 +0800 Subject: builtin-am: refuse to apply patches if index is dirty Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am will refuse to apply patches if the index is dirty. Re-implement this behavior in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index a2811b687a..537ad62501 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -14,6 +14,8 @@ #include "cache-tree.h" #include "refs.h" #include "commit.h" +#include "diff.h" +#include "diffcore.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -564,6 +566,43 @@ static void refresh_and_write_cache(void) die(_("unable to write index file")); } +/** + * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn + * branch, returns 1 if there are entries in the index, 0 otherwise. If an + * strbuf is provided, the space-separated list of files that differ will be + * appended to it. + */ +static int index_has_changes(struct strbuf *sb) +{ + unsigned char head[GIT_SHA1_RAWSZ]; + int i; + + if (!get_sha1_tree("HEAD", head)) { + struct diff_options opt; + + diff_setup(&opt); + DIFF_OPT_SET(&opt, EXIT_WITH_STATUS); + if (!sb) + DIFF_OPT_SET(&opt, QUICK); + do_diff_cache(head, &opt); + diffcore_std(&opt); + for (i = 0; sb && i < diff_queued_diff.nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); + } + diff_flush(&opt); + return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0; + } else { + for (i = 0; sb && i < active_nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, active_cache[i]->name); + } + return !!active_nr; + } +} + /** * Parses `mail` using git-mailinfo, extracting its patch and authorship info. * state->msg will be set to the patch message. state->author_name, @@ -726,9 +765,15 @@ static void do_commit(const struct am_state *state) static void am_run(struct am_state *state) { const char *argv_gc_auto[] = {"gc", "--auto", NULL}; + struct strbuf sb = STRBUF_INIT; refresh_and_write_cache(); + if (index_has_changes(&sb)) + die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf); + + strbuf_release(&sb); + while (state->cur <= state->last) { const char *mail = am_path(state, msgnum(state)); -- cgit v1.2.3 From 240bfd2de9a7aec31240300ba1d7e89c59dbafe9 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:32 +0800 Subject: builtin-am: implement --resolved/--continue Since 0c15cc9 (git-am: --resolved., 2005-11-16), git-am supported resuming from a failed patch application. The user will manually apply the patch, and the run git am --resolved which will then commit the resulting index. Re-implement this feature by introducing am_resolve(). Since it makes no sense for the user to run am --resolved when there is no session in progress, we error out in this case. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 537ad62501..fd267213aa 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -759,6 +759,21 @@ static void do_commit(const struct am_state *state) strbuf_release(&sb); } +/** + * Validates the am_state for resuming -- the "msg" and authorship fields must + * be filled up. + */ +static void validate_resume_state(const struct am_state *state) +{ + if (!state->msg) + die(_("cannot resume: %s does not exist."), + am_path(state, "final-commit")); + + if (!state->author_name || !state->author_email || !state->author_date) + die(_("cannot resume: %s does not exist."), + am_path(state, "author-script")); +} + /** * Applies all queued mail. */ @@ -813,6 +828,36 @@ next: run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); } +/** + * Resume the current am session after patch application failure. The user did + * all the hard work, and we do not have to do any patch application. Just + * trust and commit what the user has in the index and working tree. + */ +static void am_resolve(struct am_state *state) +{ + validate_resume_state(state); + + printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg); + + if (!index_has_changes(NULL)) { + printf_ln(_("No changes - did you forget to use 'git add'?\n" + "If there is nothing left to stage, chances are that something else\n" + "already introduced the same changes; you might want to skip this patch.")); + exit(128); + } + + if (unmerged_cache()) { + printf_ln(_("You still have unmerged paths in your index.\n" + "Did you forget to use 'git add'?")); + exit(128); + } + + do_commit(state); + + am_next(state); + am_run(state); +} + /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. @@ -828,13 +873,20 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int return 0; } +enum resume_mode { + RESUME_FALSE = 0, + RESUME_RESOLVED +}; + int cmd_am(int argc, const char **argv, const char *prefix) { struct am_state state; int patch_format = PATCH_FORMAT_UNKNOWN; + enum resume_mode resume = RESUME_FALSE; const char * const usage[] = { N_("git am [options] [(|)...]"), + N_("git am [options] --continue"), NULL }; @@ -842,6 +894,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), + OPT_CMDMODE(0, "continue", &resume, + N_("continue applying patches after resolving a conflict"), + RESUME_RESOLVED), + OPT_CMDMODE('r', "resolved", &resume, + N_("synonyms for --continue"), + RESUME_RESOLVED), OPT_END() }; @@ -875,6 +933,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) struct argv_array paths = ARGV_ARRAY_INIT; int i; + if (resume) + die(_("Resolve operation not in progress, we are not resuming.")); + for (i = 0; i < argc; i++) { if (is_absolute_path(argv[i]) || !prefix) argv_array_push(&paths, argv[i]); @@ -887,7 +948,16 @@ int cmd_am(int argc, const char **argv, const char *prefix) argv_array_clear(&paths); } - am_run(&state); + switch (resume) { + case RESUME_FALSE: + am_run(&state); + break; + case RESUME_RESOLVED: + am_resolve(&state); + break; + default: + die("BUG: invalid resume value"); + } am_state_release(&state); -- cgit v1.2.3 From 8c7b1563eeb2dd42eef0c5ab1a65b7a93300908c Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:33 +0800 Subject: builtin-am: don't parse mail when resuming Since 271440e (git-am: make it easier after fixing up an unapplicable patch., 2005-10-25), when "git am" is run again after being paused, the current mail message will not be re-parsed, but instead the contents of the state directory's patch, msg and author-script files will be used as-is instead. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index fd267213aa..ec579a606f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -776,8 +776,12 @@ static void validate_resume_state(const struct am_state *state) /** * Applies all queued mail. + * + * If `resume` is true, we are "resuming". The "msg" and authorship fields, as + * well as the state directory's "patch" file is used as-is for applying the + * patch and committing it. */ -static void am_run(struct am_state *state) +static void am_run(struct am_state *state, int resume) { const char *argv_gc_auto[] = {"gc", "--auto", NULL}; struct strbuf sb = STRBUF_INIT; @@ -795,11 +799,16 @@ static void am_run(struct am_state *state) if (!file_exists(mail)) goto next; - if (parse_mail(state, mail)) - goto next; /* mail should be skipped */ + if (resume) { + validate_resume_state(state); + resume = 0; + } else { + if (parse_mail(state, mail)) + goto next; /* mail should be skipped */ - write_author_script(state); - write_commit_msg(state); + write_author_script(state); + write_commit_msg(state); + } printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg); @@ -855,7 +864,7 @@ static void am_resolve(struct am_state *state) do_commit(state); am_next(state); - am_run(state); + am_run(state, 0); } /** @@ -875,6 +884,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int enum resume_mode { RESUME_FALSE = 0, + RESUME_APPLY, RESUME_RESOLVED }; @@ -927,9 +937,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) if (read_index_preload(&the_index, NULL) < 0) die(_("failed to read the index")); - if (am_in_progress(&state)) + if (am_in_progress(&state)) { + if (resume == RESUME_FALSE) + resume = RESUME_APPLY; + am_load(&state); - else { + } else { struct argv_array paths = ARGV_ARRAY_INIT; int i; @@ -950,7 +963,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) switch (resume) { case RESUME_FALSE: - am_run(&state); + am_run(&state, 0); + break; + case RESUME_APPLY: + am_run(&state, 1); break; case RESUME_RESOLVED: am_resolve(&state); -- cgit v1.2.3 From 9990080c9db0e4cb1dc0f981d85983edbf2a6f68 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:34 +0800 Subject: builtin-am: implement --skip Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am supported resuming from a failed patch application by skipping the current patch. Re-implement this feature by introducing am_skip(). Helped-by: Stefan Beller Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index ec579a606f..765844b782 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -16,6 +16,8 @@ #include "commit.h" #include "diff.h" #include "diffcore.h" +#include "unpack-trees.h" +#include "branch.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -867,6 +869,116 @@ static void am_resolve(struct am_state *state) am_run(state, 0); } +/** + * Performs a checkout fast-forward from `head` to `remote`. If `reset` is + * true, any unmerged entries will be discarded. Returns 0 on success, -1 on + * failure. + */ +static int fast_forward_to(struct tree *head, struct tree *remote, int reset) +{ + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree_desc t[2]; + + if (parse_tree(head) || parse_tree(remote)) + return -1; + + lock_file = xcalloc(1, sizeof(struct lock_file)); + hold_locked_index(lock_file, 1); + + refresh_cache(REFRESH_QUIET); + + memset(&opts, 0, sizeof(opts)); + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.update = 1; + opts.merge = 1; + opts.reset = reset; + opts.fn = twoway_merge; + init_tree_desc(&t[0], head->buffer, head->size); + init_tree_desc(&t[1], remote->buffer, remote->size); + + if (unpack_trees(2, t, &opts)) { + rollback_lock_file(lock_file); + return -1; + } + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + + return 0; +} + +/** + * Clean the index without touching entries that are not modified between + * `head` and `remote`. + */ +static int clean_index(const unsigned char *head, const unsigned char *remote) +{ + struct lock_file *lock_file; + struct tree *head_tree, *remote_tree, *index_tree; + unsigned char index[GIT_SHA1_RAWSZ]; + struct pathspec pathspec; + + head_tree = parse_tree_indirect(head); + if (!head_tree) + return error(_("Could not parse object '%s'."), sha1_to_hex(head)); + + remote_tree = parse_tree_indirect(remote); + if (!remote_tree) + return error(_("Could not parse object '%s'."), sha1_to_hex(remote)); + + read_cache_unmerged(); + + if (fast_forward_to(head_tree, head_tree, 1)) + return -1; + + if (write_cache_as_tree(index, 0, NULL)) + return -1; + + index_tree = parse_tree_indirect(index); + if (!index_tree) + return error(_("Could not parse object '%s'."), sha1_to_hex(index)); + + if (fast_forward_to(index_tree, remote_tree, 0)) + return -1; + + memset(&pathspec, 0, sizeof(pathspec)); + + lock_file = xcalloc(1, sizeof(struct lock_file)); + hold_locked_index(lock_file, 1); + + if (read_tree(remote_tree, 0, &pathspec)) { + rollback_lock_file(lock_file); + return -1; + } + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + + remove_branch_state(); + + return 0; +} + +/** + * Resume the current am session by skipping the current patch. + */ +static void am_skip(struct am_state *state) +{ + unsigned char head[GIT_SHA1_RAWSZ]; + + if (get_sha1("HEAD", head)) + hashcpy(head, EMPTY_TREE_SHA1_BIN); + + if (clean_index(head, head)) + die(_("failed to clean index")); + + am_next(state); + am_run(state, 0); +} + /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. @@ -885,7 +997,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int enum resume_mode { RESUME_FALSE = 0, RESUME_APPLY, - RESUME_RESOLVED + RESUME_RESOLVED, + RESUME_SKIP }; int cmd_am(int argc, const char **argv, const char *prefix) @@ -896,7 +1009,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) const char * const usage[] = { N_("git am [options] [(|)...]"), - N_("git am [options] --continue"), + N_("git am [options] (--continue | --skip)"), NULL }; @@ -910,6 +1023,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE('r', "resolved", &resume, N_("synonyms for --continue"), RESUME_RESOLVED), + OPT_CMDMODE(0, "skip", &resume, + N_("skip the current patch"), + RESUME_SKIP), OPT_END() }; @@ -971,6 +1087,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) case RESUME_RESOLVED: am_resolve(&state); break; + case RESUME_SKIP: + am_skip(&state); + break; default: die("BUG: invalid resume value"); } -- cgit v1.2.3 From 33388a71d23e6a296eb33879e418e857444f2d74 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:35 +0800 Subject: builtin-am: implement --abort Since 3e5057a (git am --abort, 2008-07-16), git-am supported the --abort option that will rewind HEAD back to the original commit. Re-implement this through am_abort(). Since 7b3b7e3 (am --abort: keep unrelated commits since the last failure and warn, 2010-12-21), to prevent commits made since the last failure from being lost, git-am will not rewind HEAD back to the original commit if HEAD moved since the last failure. Re-implement this through safe_to_abort(). Helped-by: Stefan Beller Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 765844b782..6c24d075c2 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -490,6 +490,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, static void am_setup(struct am_state *state, enum patch_format patch_format, const char **paths) { + unsigned char curr_head[GIT_SHA1_RAWSZ]; + if (!patch_format) patch_format = detect_patch_format(paths); @@ -506,6 +508,14 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, die(_("Failed to split patches.")); } + if (!get_sha1("HEAD", curr_head)) { + write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head)); + update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); + } else { + write_file(am_path(state, "abort-safety"), 1, "%s", ""); + delete_ref("ORIG_HEAD", NULL, 0); + } + /* * NOTE: Since the "next" and "last" files determine if an am_state * session is in progress, they should be written last. @@ -522,6 +532,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, */ static void am_next(struct am_state *state) { + unsigned char head[GIT_SHA1_RAWSZ]; + free(state->author_name); state->author_name = NULL; @@ -538,6 +550,11 @@ static void am_next(struct am_state *state) unlink(am_path(state, "author-script")); unlink(am_path(state, "final-commit")); + if (!get_sha1("HEAD", head)) + write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head)); + else + write_file(am_path(state, "abort-safety"), 1, "%s", ""); + state->cur++; write_file(am_path(state, "next"), 1, "%d", state->cur); } @@ -788,10 +805,14 @@ static void am_run(struct am_state *state, int resume) const char *argv_gc_auto[] = {"gc", "--auto", NULL}; struct strbuf sb = STRBUF_INIT; + unlink(am_path(state, "dirtyindex")); + refresh_and_write_cache(); - if (index_has_changes(&sb)) + if (index_has_changes(&sb)) { + write_file(am_path(state, "dirtyindex"), 1, "t"); die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf); + } strbuf_release(&sb); @@ -979,6 +1000,75 @@ static void am_skip(struct am_state *state) am_run(state, 0); } +/** + * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise. + * + * It is not safe to reset HEAD when: + * 1. git-am previously failed because the index was dirty. + * 2. HEAD has moved since git-am previously failed. + */ +static int safe_to_abort(const struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ]; + + if (file_exists(am_path(state, "dirtyindex"))) + return 0; + + if (read_state_file(&sb, state, "abort-safety", 1) > 0) { + if (get_sha1_hex(sb.buf, abort_safety)) + die(_("could not parse %s"), am_path(state, "abort_safety")); + } else + hashclr(abort_safety); + + if (get_sha1("HEAD", head)) + hashclr(head); + + if (!hashcmp(head, abort_safety)) + return 1; + + error(_("You seem to have moved HEAD since the last 'am' failure.\n" + "Not rewinding to ORIG_HEAD")); + + return 0; +} + +/** + * Aborts the current am session if it is safe to do so. + */ +static void am_abort(struct am_state *state) +{ + unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ]; + int has_curr_head, has_orig_head; + char *curr_branch; + + if (!safe_to_abort(state)) { + am_destroy(state); + return; + } + + curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL); + has_curr_head = !is_null_sha1(curr_head); + if (!has_curr_head) + hashcpy(curr_head, EMPTY_TREE_SHA1_BIN); + + has_orig_head = !get_sha1("ORIG_HEAD", orig_head); + if (!has_orig_head) + hashcpy(orig_head, EMPTY_TREE_SHA1_BIN); + + clean_index(curr_head, orig_head); + + if (has_orig_head) + update_ref("am --abort", "HEAD", orig_head, + has_curr_head ? curr_head : NULL, 0, + UPDATE_REFS_DIE_ON_ERR); + else if (curr_branch) + delete_ref(curr_branch, NULL, REF_NODEREF); + + free(curr_branch); + am_destroy(state); +} + /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. @@ -998,7 +1088,8 @@ enum resume_mode { RESUME_FALSE = 0, RESUME_APPLY, RESUME_RESOLVED, - RESUME_SKIP + RESUME_SKIP, + RESUME_ABORT }; int cmd_am(int argc, const char **argv, const char *prefix) @@ -1009,7 +1100,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) const char * const usage[] = { N_("git am [options] [(|)...]"), - N_("git am [options] (--continue | --skip)"), + N_("git am [options] (--continue | --skip | --abort)"), NULL }; @@ -1026,6 +1117,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "skip", &resume, N_("skip the current patch"), RESUME_SKIP), + OPT_CMDMODE(0, "abort", &resume, + N_("restore the original branch and abort the patching operation."), + RESUME_ABORT), OPT_END() }; @@ -1090,6 +1184,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) case RESUME_SKIP: am_skip(&state); break; + case RESUME_ABORT: + am_abort(&state); + break; default: die("BUG: invalid resume value"); } -- cgit v1.2.3 From 8d185503186ea0bc906defb9ecb1e9fb0e06efae Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:36 +0800 Subject: builtin-am: reject patches when there's a session in progress Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am would error out if the user gave it mbox(s) on the command-line, but there was a session in progress. Since c95b138 (Fix git-am safety checks, 2006-09-15), git-am would detect if the user attempted to feed it a mbox via stdin, by checking if stdin is not a tty and there is no resume command given. Re-implement the above two safety checks. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 6c24d075c2..d4b4b86be9 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1148,6 +1148,21 @@ int cmd_am(int argc, const char **argv, const char *prefix) die(_("failed to read the index")); if (am_in_progress(&state)) { + /* + * Catch user error to feed us patches when there is a session + * in progress: + * + * 1. mbox path(s) are provided on the command-line. + * 2. stdin is not a tty: the user is trying to feed us a patch + * from standard input. This is somewhat unreliable -- stdin + * could be /dev/null for example and the caller did not + * intend to feed us a patch but wanted to continue + * unattended. + */ + if (argc || (resume == RESUME_FALSE && !isatty(0))) + die(_("previous rebase directory %s still exists but mbox given."), + state.dir); + if (resume == RESUME_FALSE) resume = RESUME_APPLY; -- cgit v1.2.3 From 5d28cf78196acc454b4430b1efe3b5f9e55ae2e6 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:37 +0800 Subject: builtin-am: implement -q/--quiet Since 0e987a1 (am, rebase: teach quiet option, 2009-06-16), git-am supported the --quiet option, and when told to be quiet, would only speak on failure. Re-implement this by introducing the say() function, which works like fprintf_ln(), but would only write to the stream when state->quiet is false. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index d4b4b86be9..0875e69d55 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -80,6 +80,9 @@ struct am_state { /* number of digits in patch filename */ int prec; + + /* various operating modes and command line options */ + int quiet; }; /** @@ -116,6 +119,22 @@ static inline const char *am_path(const struct am_state *state, const char *path return mkpath("%s/%s", state->dir, path); } +/** + * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline + * at the end. + */ +static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (!state->quiet) { + vfprintf(fp, fmt, ap); + putc('\n', fp); + } + va_end(ap); +} + /** * Returns 1 if there is an am session in progress, 0 otherwise. */ @@ -330,6 +349,9 @@ static void am_load(struct am_state *state) read_commit_msg(state); + read_state_file(&sb, state, "quiet", 1); + state->quiet = !strcmp(sb.buf, "t"); + strbuf_release(&sb); } @@ -508,6 +530,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, die(_("Failed to split patches.")); } + write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); + if (!get_sha1("HEAD", curr_head)) { write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head)); update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); @@ -756,7 +780,7 @@ static void do_commit(const struct am_state *state) commit_list_insert(lookup_commit(parent), &parents); } else { ptr = NULL; - fprintf_ln(stderr, _("applying to an empty history")); + say(state, stderr, _("applying to an empty history")); } author = fmt_ident(state->author_name, state->author_email, @@ -833,7 +857,7 @@ static void am_run(struct am_state *state, int resume) write_commit_msg(state); } - printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg); + say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); if (run_apply(state) < 0) { int advice_amworkdir = 1; @@ -869,7 +893,7 @@ static void am_resolve(struct am_state *state) { validate_resume_state(state); - printf_ln(_("Applying: %.*s"), linelen(state->msg), state->msg); + say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); if (!index_has_changes(NULL)) { printf_ln(_("No changes - did you forget to use 'git add'?\n" @@ -1105,6 +1129,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) }; struct option options[] = { + OPT__QUIET(&state.quiet, N_("be quiet")), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From 2d83109aabbb8c660c27cbf03ec67cb88822639f Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:38 +0800 Subject: builtin-am: exit with user friendly message on failure Since ced9456 (Give the user a hint for how to continue in the case that git-am fails because it requires user intervention, 2006-05-02), git-am prints additional information on how the user can re-invoke git-am to resume patch application after resolving the failure. Re-implement this through the die_user_resolve() function. Since cc12005 (Make git rebase interactive help match documentation., 2006-05-13), git-am supports the --resolvemsg option which is used by git-rebase to override the message printed out when git-am fails. Re-implement this option. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 0875e69d55..8b8f2da7f4 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -83,6 +83,7 @@ struct am_state { /* various operating modes and command line options */ int quiet; + const char *resolvemsg; }; /** @@ -646,6 +647,25 @@ static int index_has_changes(struct strbuf *sb) } } +/** + * Dies with a user-friendly message on how to proceed after resolving the + * problem. This message can be overridden with state->resolvemsg. + */ +static void NORETURN die_user_resolve(const struct am_state *state) +{ + if (state->resolvemsg) { + printf_ln("%s", state->resolvemsg); + } else { + const char *cmdline = "git am"; + + printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline); + printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline); + printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline); + } + + exit(128); +} + /** * Parses `mail` using git-mailinfo, extracting its patch and authorship info. * state->msg will be set to the patch message. state->author_name, @@ -706,7 +726,7 @@ static int parse_mail(struct am_state *state, const char *mail) if (is_empty_file(am_path(state, "patch"))) { printf_ln(_("Patch is empty. Was it split wrong?")); - exit(128); + die_user_resolve(state); } strbuf_addstr(&msg, "\n\n"); @@ -871,7 +891,7 @@ static void am_run(struct am_state *state, int resume) printf_ln(_("The copy of the patch that failed is found in: %s"), am_path(state, "patch")); - exit(128); + die_user_resolve(state); } do_commit(state); @@ -899,13 +919,13 @@ static void am_resolve(struct am_state *state) printf_ln(_("No changes - did you forget to use 'git add'?\n" "If there is nothing left to stage, chances are that something else\n" "already introduced the same changes; you might want to skip this patch.")); - exit(128); + die_user_resolve(state); } if (unmerged_cache()) { printf_ln(_("You still have unmerged paths in your index.\n" "Did you forget to use 'git add'?")); - exit(128); + die_user_resolve(state); } do_commit(state); @@ -1133,6 +1153,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), + OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, + N_("override error message when patch failure occurs")), OPT_CMDMODE(0, "continue", &resume, N_("continue applying patches after resolving a conflict"), RESUME_RESOLVED), -- cgit v1.2.3 From eb898b83f2417b99f290459884f6ea8d2db2bd4d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:39 +0800 Subject: builtin-am: implement -s/--signoff Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am supported the --signoff option which will append a signoff at the end of the commit messsage. Re-implement this feature in parse_mail() by calling append_signoff() if the option is set. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 8b8f2da7f4..12952cf11c 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -18,6 +18,7 @@ #include "diffcore.h" #include "unpack-trees.h" #include "branch.h" +#include "sequencer.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -83,6 +84,7 @@ struct am_state { /* various operating modes and command line options */ int quiet; + int signoff; const char *resolvemsg; }; @@ -353,6 +355,9 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "quiet", 1); state->quiet = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "sign", 1); + state->signoff = !strcmp(sb.buf, "t"); + strbuf_release(&sb); } @@ -533,6 +538,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); + write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f"); + if (!get_sha1("HEAD", curr_head)) { write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head)); update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); @@ -734,6 +741,9 @@ static int parse_mail(struct am_state *state, const char *mail) die_errno(_("could not read '%s'"), am_path(state, "msg")); stripspace(&msg, 0); + if (state->signoff) + append_signoff(&msg, 0, 0); + assert(!state->author_name); state->author_name = strbuf_detach(&author_name, NULL); @@ -1150,6 +1160,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__QUIET(&state.quiet, N_("be quiet")), + OPT_BOOL('s', "signoff", &state.signoff, + N_("add a Signed-off-by line to the commit message")), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From d23a5117f821b91e1d72745238a4c085a089864e Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:40 +0800 Subject: cache-tree: introduce write_index_as_tree() A caller may wish to write a temporary index as a tree. However, write_cache_as_tree() assumes that the index was read from, and will write to, the default index file path. Introduce write_index_as_tree() which removes this limitation by allowing the caller to specify its own index_state and index file path. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- cache-tree.c | 29 +++++++++++++++++------------ cache-tree.h | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/cache-tree.c b/cache-tree.c index 32772b9564..feace8bd90 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -592,7 +592,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat return it; } -int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) +int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix) { int entries, was_valid, newfd; struct lock_file *lock_file; @@ -603,23 +603,23 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) */ lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_locked_index(lock_file, 1); + newfd = hold_lock_file_for_update(lock_file, index_path, LOCK_DIE_ON_ERROR); - entries = read_cache(); + entries = read_index_from(index_state, index_path); if (entries < 0) return WRITE_TREE_UNREADABLE_INDEX; if (flags & WRITE_TREE_IGNORE_CACHE_TREE) - cache_tree_free(&(active_cache_tree)); + cache_tree_free(&index_state->cache_tree); - if (!active_cache_tree) - active_cache_tree = cache_tree(); + if (!index_state->cache_tree) + index_state->cache_tree = cache_tree(); - was_valid = cache_tree_fully_valid(active_cache_tree); + was_valid = cache_tree_fully_valid(index_state->cache_tree); if (!was_valid) { - if (cache_tree_update(&the_index, flags) < 0) + if (cache_tree_update(index_state, flags) < 0) return WRITE_TREE_UNMERGED_INDEX; if (0 <= newfd) { - if (!write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (!write_locked_index(index_state, lock_file, COMMIT_LOCK)) newfd = -1; } /* Not being able to write is fine -- we are only interested @@ -631,14 +631,14 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) } if (prefix) { - struct cache_tree *subtree = - cache_tree_find(active_cache_tree, prefix); + struct cache_tree *subtree; + subtree = cache_tree_find(index_state->cache_tree, prefix); if (!subtree) return WRITE_TREE_PREFIX_ERROR; hashcpy(sha1, subtree->sha1); } else - hashcpy(sha1, active_cache_tree->sha1); + hashcpy(sha1, index_state->cache_tree->sha1); if (0 <= newfd) rollback_lock_file(lock_file); @@ -646,6 +646,11 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) return 0; } +int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) +{ + return write_index_as_tree(sha1, &the_index, get_index_file(), flags, prefix); +} + static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) { struct tree_desc desc; diff --git a/cache-tree.h b/cache-tree.h index aa7b3e4a0a..41c574663a 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -46,6 +46,7 @@ int update_main_cache_tree(int); #define WRITE_TREE_UNMERGED_INDEX (-2) #define WRITE_TREE_PREFIX_ERROR (-3) +int write_index_as_tree(unsigned char *sha1, struct index_state *index_state, const char *index_path, int flags, const char *prefix); int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix); void prime_cache_tree(struct index_state *, struct tree *); -- cgit v1.2.3 From 84f3de28baf6fc6a91b95fad9a4c5468dec3ea8e Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:41 +0800 Subject: builtin-am: implement --3way Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh supported the --3way option, and if set, would attempt to do a 3-way merge if the initial patch application fails. Since 5d86861 (am -3: list the paths that needed 3-way fallback, 2012-03-28), in a 3-way merge git-am.sh would list the paths that needed 3-way fallback, so that the user can review them more carefully to spot mismerges. Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 12952cf11c..a5d5e8c665 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -19,6 +19,8 @@ #include "unpack-trees.h" #include "branch.h" #include "sequencer.h" +#include "revision.h" +#include "merge-recursive.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -83,6 +85,7 @@ struct am_state { int prec; /* various operating modes and command line options */ + int threeway; int quiet; int signoff; const char *resolvemsg; @@ -352,6 +355,9 @@ static void am_load(struct am_state *state) read_commit_msg(state); + read_state_file(&sb, state, "threeway", 1); + state->threeway = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "quiet", 1); state->quiet = !strcmp(sb.buf, "t"); @@ -536,6 +542,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, die(_("Failed to split patches.")); } + write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f"); + write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f"); @@ -766,25 +774,141 @@ finish: } /** - * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. + * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If + * `index_file` is not NULL, the patch will be applied to that index. */ -static int run_apply(const struct am_state *state) +static int run_apply(const struct am_state *state, const char *index_file) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + if (index_file) + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file); + + /* + * If we are allowed to fall back on 3-way merge, don't give false + * errors during the initial attempt. + */ + if (state->threeway && !index_file) { + cp.no_stdout = 1; + cp.no_stderr = 1; + } + argv_array_push(&cp.args, "apply"); - argv_array_push(&cp.args, "--index"); + + if (index_file) + argv_array_push(&cp.args, "--cached"); + else + argv_array_push(&cp.args, "--index"); + argv_array_push(&cp.args, am_path(state, "patch")); if (run_command(&cp)) return -1; /* Reload index as git-apply will have modified it. */ + discard_cache(); + read_cache_from(index_file ? index_file : get_index_file()); + + return 0; +} + +/** + * Builds an index that contains just the blobs needed for a 3way merge. + */ +static int build_fake_ancestor(const struct am_state *state, const char *index_file) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_push(&cp.args, "apply"); + argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); + argv_array_push(&cp.args, am_path(state, "patch")); + + if (run_command(&cp)) + return -1; + + return 0; +} + +/** + * Attempt a threeway merge, using index_path as the temporary index. + */ +static int fall_back_threeway(const struct am_state *state, const char *index_path) +{ + unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ], + our_tree[GIT_SHA1_RAWSZ]; + const unsigned char *bases[1] = {orig_tree}; + struct merge_options o; + struct commit *result; + char *his_tree_name; + + if (get_sha1("HEAD", our_tree) < 0) + hashcpy(our_tree, EMPTY_TREE_SHA1_BIN); + + if (build_fake_ancestor(state, index_path)) + return error("could not build fake ancestor"); + + discard_cache(); + read_cache_from(index_path); + + if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL)) + return error(_("Repository lacks necessary blobs to fall back on 3-way merge.")); + + say(state, stdout, _("Using index info to reconstruct a base tree...")); + + if (!state->quiet) { + /* + * List paths that needed 3-way fallback, so that the user can + * review them with extra care to spot mismerges. + */ + struct rev_info rev_info; + const char *diff_filter_str = "--diff-filter=AM"; + + init_revisions(&rev_info, NULL); + rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS; + diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1); + add_pending_sha1(&rev_info, "HEAD", our_tree, 0); + diff_setup_done(&rev_info.diffopt); + run_diff_index(&rev_info, 1); + } + + if (run_apply(state, index_path)) + return error(_("Did you hand edit your patch?\n" + "It does not apply to blobs recorded in its index.")); + + if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL)) + return error("could not write tree"); + + say(state, stdout, _("Falling back to patching base and 3-way merge...")); + discard_cache(); read_cache(); + /* + * 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 + * recursive ends up canceling them, saying that we reverted all those + * changes. + */ + + init_merge_options(&o); + + o.branch1 = "HEAD"; + his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); + o.branch2 = his_tree_name; + + if (state->quiet) + o.verbosity = 0; + + if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) { + free(his_tree_name); + return error(_("Failed to merge in the changes.")); + } + + free(his_tree_name); return 0; } @@ -872,6 +996,7 @@ static void am_run(struct am_state *state, int resume) while (state->cur <= state->last) { const char *mail = am_path(state, msgnum(state)); + int apply_status; if (!file_exists(mail)) goto next; @@ -889,7 +1014,26 @@ static void am_run(struct am_state *state, int resume) say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); - if (run_apply(state) < 0) { + apply_status = run_apply(state, NULL); + + if (apply_status && state->threeway) { + struct strbuf sb = STRBUF_INIT; + + strbuf_addstr(&sb, am_path(state, "patch-merge-index")); + apply_status = fall_back_threeway(state, sb.buf); + strbuf_release(&sb); + + /* + * Applying the patch to an earlier tree and merging + * the result may have produced the same tree as ours. + */ + if (!apply_status && !index_has_changes(NULL)) { + say(state, stdout, _("No changes -- Patch already applied.")); + goto next; + } + } + + if (apply_status) { int advice_amworkdir = 1; printf_ln(_("Patch failed at %s %.*s"), msgnum(state), @@ -1159,6 +1303,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) }; struct option options[] = { + OPT_BOOL('3', "3way", &state.threeway, + N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), OPT_BOOL('s', "signoff", &state.signoff, N_("add a Signed-off-by line to the commit message")), -- cgit v1.2.3 From 35bdcc59f6fa29699c106123d48bbd96938aba6d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:42 +0800 Subject: builtin-am: implement --rebasing mode Since 3041c32 (am: --rebasing, 2008-03-04), git-am.sh supported the --rebasing option, which is used internally by git-rebase to tell git-am that it is being used for its purpose. It would create the empty file $state_dir/rebasing to help "completion" scripts tell if the ongoing operation is am or rebase. As of 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), --rebasing also implies --3way as well. Since a1549e1 (am: return control to caller, for housekeeping, 2013-05-12), git-am.sh would only clean up the state directory when it is not --rebasing, instead deferring cleanup to git-rebase.sh. Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index a5d5e8c665..440a65361a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -89,6 +89,7 @@ struct am_state { int quiet; int signoff; const char *resolvemsg; + int rebasing; }; /** @@ -364,6 +365,8 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "sign", 1); state->signoff = !strcmp(sb.buf, "t"); + state->rebasing = !!file_exists(am_path(state, "rebasing")); + strbuf_release(&sb); } @@ -542,18 +545,29 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, die(_("Failed to split patches.")); } + if (state->rebasing) + state->threeway = 1; + write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f"); write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f"); + if (state->rebasing) + write_file(am_path(state, "rebasing"), 1, "%s", ""); + else + write_file(am_path(state, "applying"), 1, "%s", ""); + if (!get_sha1("HEAD", curr_head)) { write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head)); - update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); + if (!state->rebasing) + update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); } else { write_file(am_path(state, "abort-safety"), 1, "%s", ""); - delete_ref("ORIG_HEAD", NULL, 0); + if (!state->rebasing) + delete_ref("ORIG_HEAD", NULL, 0); } /* @@ -1054,8 +1068,14 @@ next: am_next(state); } - am_destroy(state); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + /* + * In rebasing mode, it's up to the caller to take care of + * housekeeping. + */ + if (!state->rebasing) { + am_destroy(state); + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } } /** @@ -1325,6 +1345,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), + OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, + N_("(internal use for git-rebase)")), OPT_END() }; -- cgit v1.2.3 From df2760a576a8d26e26a6a11aa497e347c7402fd3 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:43 +0800 Subject: builtin-am: bypass git-mailinfo when --rebasing Since 5e835ca (rebase: do not munge commit log message, 2008-04-16), git am --rebasing no longer gets the commit log message from the patch, but reads it directly from the commit identified by the "From " header line. Since 43c2325 (am: use get_author_ident_from_commit instead of mailinfo when rebasing, 2010-06-16), git am --rebasing also gets the author name, email and date directly from the commit. Since 0fbb95d (am: don't call mailinfo if $rebasing, 2012-06-26), git am --rebasing does not use git-mailinfo to get the patch body, but rather generates it directly from the commit itself. The above 3 commits introduced a separate parse_mail() code path in git-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement this code path in builtin/am.c as parse_mail_rebase(). Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 440a65361a..a02c84ea8f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -21,6 +21,8 @@ #include "sequencer.h" #include "revision.h" #include "merge-recursive.h" +#include "revision.h" +#include "log-tree.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -787,6 +789,129 @@ finish: return ret; } +/** + * Sets commit_id to the commit hash where the mail was generated from. + * Returns 0 on success, -1 on failure. + */ +static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail) +{ + struct strbuf sb = STRBUF_INIT; + FILE *fp = xfopen(mail, "r"); + const char *x; + + if (strbuf_getline(&sb, fp, '\n')) + return -1; + + if (!skip_prefix(sb.buf, "From ", &x)) + return -1; + + if (get_sha1_hex(x, commit_id) < 0) + return -1; + + strbuf_release(&sb); + fclose(fp); + return 0; +} + +/** + * Sets state->msg, state->author_name, state->author_email, state->author_date + * to the commit's respective info. + */ +static void get_commit_info(struct am_state *state, struct commit *commit) +{ + const char *buffer, *ident_line, *author_date, *msg; + size_t ident_len; + struct ident_split ident_split; + struct strbuf sb = STRBUF_INIT; + + buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding()); + + ident_line = find_commit_header(buffer, "author", &ident_len); + + if (split_ident_line(&ident_split, ident_line, ident_len) < 0) { + strbuf_add(&sb, ident_line, ident_len); + die(_("invalid ident line: %s"), sb.buf); + } + + assert(!state->author_name); + if (ident_split.name_begin) { + strbuf_add(&sb, ident_split.name_begin, + ident_split.name_end - ident_split.name_begin); + state->author_name = strbuf_detach(&sb, NULL); + } else + state->author_name = xstrdup(""); + + assert(!state->author_email); + if (ident_split.mail_begin) { + strbuf_add(&sb, ident_split.mail_begin, + ident_split.mail_end - ident_split.mail_begin); + state->author_email = strbuf_detach(&sb, NULL); + } else + state->author_email = xstrdup(""); + + author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL)); + strbuf_addstr(&sb, author_date); + assert(!state->author_date); + state->author_date = strbuf_detach(&sb, NULL); + + assert(!state->msg); + msg = strstr(buffer, "\n\n"); + if (!msg) + die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1)); + state->msg = xstrdup(msg + 2); + state->msg_len = strlen(state->msg); +} + +/** + * Writes `commit` as a patch to the state directory's "patch" file. + */ +static void write_commit_patch(const struct am_state *state, struct commit *commit) +{ + struct rev_info rev_info; + FILE *fp; + + fp = xfopen(am_path(state, "patch"), "w"); + init_revisions(&rev_info, NULL); + rev_info.diff = 1; + rev_info.abbrev = 0; + rev_info.disable_stdin = 1; + rev_info.show_root_diff = 1; + rev_info.diffopt.output_format = DIFF_FORMAT_PATCH; + rev_info.no_commit_id = 1; + DIFF_OPT_SET(&rev_info.diffopt, BINARY); + DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX); + rev_info.diffopt.use_color = 0; + rev_info.diffopt.file = fp; + rev_info.diffopt.close_file = 1; + add_pending_object(&rev_info, &commit->object, ""); + diff_setup_done(&rev_info.diffopt); + log_tree_commit(&rev_info, commit); +} + +/** + * Like parse_mail(), but parses the mail by looking up its commit ID + * directly. This is used in --rebasing mode to bypass git-mailinfo's munging + * of patches. + * + * Will always return 0 as the patch should never be skipped. + */ +static int parse_mail_rebase(struct am_state *state, const char *mail) +{ + struct commit *commit; + unsigned char commit_sha1[GIT_SHA1_RAWSZ]; + + if (get_mail_commit_sha1(commit_sha1, mail) < 0) + die(_("could not parse %s"), mail); + + commit = lookup_commit_or_die(commit_sha1, mail); + + get_commit_info(state, commit); + + write_commit_patch(state, commit); + + return 0; +} + /** * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If * `index_file` is not NULL, the patch will be applied to that index. @@ -1019,7 +1144,14 @@ static void am_run(struct am_state *state, int resume) validate_resume_state(state); resume = 0; } else { - if (parse_mail(state, mail)) + int skip; + + if (state->rebasing) + skip = parse_mail_rebase(state, mail); + else + skip = parse_mail(state, mail); + + if (skip) goto next; /* mail should be skipped */ write_author_script(state); -- cgit v1.2.3 From 6d42ac2941cac80b44e318d867bae7979c1af6fe Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:44 +0800 Subject: builtin-am: handle stray state directory Should git-am terminate unexpectedly between the point where the state directory is created, but the "next" and "last" files are not written yet, a stray state directory will be left behind. As such, since b141f3c (am: handle stray $dotest directory, 2013-06-15), git-am.sh explicitly recognizes such a stray directory, and allows the user to remove it with am --abort. Re-implement this feature in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index a02c84ea8f..47dd4c7ddf 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1530,6 +1530,23 @@ int cmd_am(int argc, const char **argv, const char *prefix) struct argv_array paths = ARGV_ARRAY_INIT; int i; + /* + * Handle stray state directory in the independent-run case. In + * the --rebasing case, it is up to the caller to take care of + * stray directories. + */ + if (file_exists(state.dir) && !state.rebasing) { + if (resume == RESUME_ABORT) { + am_destroy(&state); + am_state_release(&state); + return 0; + } + + die(_("Stray %s directory found.\n" + "Use \"git am --abort\" to remove it."), + state.dir); + } + if (resume) die(_("Resolve operation not in progress, we are not resuming.")); -- cgit v1.2.3 From ef7ee16d75855fb17578e3f29e33f17ba107aac1 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:45 +0800 Subject: builtin-am: implement -u/--utf8 Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh supported the -u,--utf8 option. If set, the -u option will be passed to git-mailinfo to re-code the commit log message and authorship in the charset specified by i18n.commitencoding. If unset, the -n option will be passed to git-mailinfo, which disables the re-encoding. Since d84029b (--utf8 is now default for 'git-am', 2007-01-08), --utf8 is specified by default in git-am.sh. Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 47dd4c7ddf..528b2c94f4 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -90,6 +90,7 @@ struct am_state { int threeway; int quiet; int signoff; + int utf8; const char *resolvemsg; int rebasing; }; @@ -106,6 +107,8 @@ static void am_state_init(struct am_state *state, const char *dir) state->dir = xstrdup(dir); state->prec = 4; + + state->utf8 = 1; } /** @@ -367,6 +370,9 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "sign", 1); state->signoff = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "utf8", 1); + state->utf8 = !strcmp(sb.buf, "t"); + state->rebasing = !!file_exists(am_path(state, "rebasing")); strbuf_release(&sb); @@ -556,6 +562,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f"); + write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f"); + if (state->rebasing) write_file(am_path(state, "rebasing"), 1, "%s", ""); else @@ -722,6 +730,7 @@ static int parse_mail(struct am_state *state, const char *mail) cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777); argv_array_push(&cp.args, "mailinfo"); + argv_array_push(&cp.args, state->utf8 ? "-u" : "-n"); argv_array_push(&cp.args, am_path(state, "msg")); argv_array_push(&cp.args, am_path(state, "patch")); @@ -1460,6 +1469,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT__QUIET(&state.quiet, N_("be quiet")), OPT_BOOL('s', "signoff", &state.signoff, N_("add a Signed-off-by line to the commit message")), + OPT_BOOL('u', "utf8", &state.utf8, + N_("recode into utf8 (default)")), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From 4f1b69617577309d361a006fc77d71a5cff7ec06 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:46 +0800 Subject: builtin-am: implement -k/--keep, --keep-non-patch Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh supported the -k/--keep option to pass the -k option to git-mailsplit. Since f7e5ea1 (am: learn passing -b to mailinfo, 2012-01-16), git-am.sh supported the --keep-non-patch option to pass the -b option to git-mailsplit. Re-implement these two options in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 528b2c94f4..68dca2e8d3 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -68,6 +68,12 @@ enum patch_format { PATCH_FORMAT_MBOX }; +enum keep_type { + KEEP_FALSE = 0, + KEEP_TRUE, /* pass -k flag to git-mailinfo */ + KEEP_NON_PATCH /* pass -b flag to git-mailinfo */ +}; + struct am_state { /* state directory path */ char *dir; @@ -91,6 +97,7 @@ struct am_state { int quiet; int signoff; int utf8; + int keep; /* enum keep_type */ const char *resolvemsg; int rebasing; }; @@ -373,6 +380,14 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "utf8", 1); state->utf8 = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "keep", 1); + if (!strcmp(sb.buf, "t")) + state->keep = KEEP_TRUE; + else if (!strcmp(sb.buf, "b")) + state->keep = KEEP_NON_PATCH; + else + state->keep = KEEP_FALSE; + state->rebasing = !!file_exists(am_path(state, "rebasing")); strbuf_release(&sb); @@ -536,6 +551,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, const char **paths) { unsigned char curr_head[GIT_SHA1_RAWSZ]; + const char *str; if (!patch_format) patch_format = detect_patch_format(paths); @@ -564,6 +580,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f"); + switch (state->keep) { + case KEEP_FALSE: + str = "f"; + break; + case KEEP_TRUE: + str = "t"; + break; + case KEEP_NON_PATCH: + str = "b"; + break; + default: + die("BUG: invalid value for state->keep"); + } + + write_file(am_path(state, "keep"), 1, "%s", str); + if (state->rebasing) write_file(am_path(state, "rebasing"), 1, "%s", ""); else @@ -731,6 +763,20 @@ static int parse_mail(struct am_state *state, const char *mail) argv_array_push(&cp.args, "mailinfo"); argv_array_push(&cp.args, state->utf8 ? "-u" : "-n"); + + switch (state->keep) { + case KEEP_FALSE: + break; + case KEEP_TRUE: + argv_array_push(&cp.args, "-k"); + break; + case KEEP_NON_PATCH: + argv_array_push(&cp.args, "-b"); + break; + default: + die("BUG: invalid value for state->keep"); + } + argv_array_push(&cp.args, am_path(state, "msg")); argv_array_push(&cp.args, am_path(state, "patch")); @@ -1471,6 +1517,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("add a Signed-off-by line to the commit message")), OPT_BOOL('u', "utf8", &state.utf8, N_("recode into utf8 (default)")), + OPT_SET_INT('k', "keep", &state.keep, + N_("pass -k flag to git-mailinfo"), KEEP_TRUE), + OPT_SET_INT(0, "keep-non-patch", &state.keep, + N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From 702cbaad61a15b035fd022bae57403baeb324d82 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:47 +0800 Subject: builtin-am: implement --[no-]message-id, am.messageid Since a078f73 (git-am: add --message-id/--no-message-id, 2014-11-25), git-am.sh supported the --[no-]message-id options, and the "am.messageid" setting which specifies the default option. --[no-]message-id tells git-am whether or not the -m option should be passed to git-mailinfo. Re-implement this option in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 68dca2e8d3..8e978395b2 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -98,6 +98,7 @@ struct am_state { int signoff; int utf8; int keep; /* enum keep_type */ + int message_id; const char *resolvemsg; int rebasing; }; @@ -116,6 +117,8 @@ static void am_state_init(struct am_state *state, const char *dir) state->prec = 4; state->utf8 = 1; + + git_config_get_bool("am.messageid", &state->message_id); } /** @@ -388,6 +391,9 @@ static void am_load(struct am_state *state) else state->keep = KEEP_FALSE; + read_state_file(&sb, state, "messageid", 1); + state->message_id = !strcmp(sb.buf, "t"); + state->rebasing = !!file_exists(am_path(state, "rebasing")); strbuf_release(&sb); @@ -596,6 +602,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "keep"), 1, "%s", str); + write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f"); + if (state->rebasing) write_file(am_path(state, "rebasing"), 1, "%s", ""); else @@ -777,6 +785,9 @@ static int parse_mail(struct am_state *state, const char *mail) die("BUG: invalid value for state->keep"); } + if (state->message_id) + argv_array_push(&cp.args, "-m"); + argv_array_push(&cp.args, am_path(state, "msg")); argv_array_push(&cp.args, am_path(state, "patch")); @@ -1521,6 +1532,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("pass -k flag to git-mailinfo"), KEEP_TRUE), OPT_SET_INT(0, "keep-non-patch", &state.keep, N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH), + OPT_BOOL('m', "message-id", &state.message_id, + N_("pass -m flag to git-mailinfo")), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From 5d123a4017813540ad67656608c5d85e84cc8e5d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:48 +0800 Subject: builtin-am: support --keep-cr, am.keepcr Since ad2c928 (git-am: Add command line parameter `--keep-cr` passing it to git-mailsplit, 2010-02-27), git-am.sh supported the --keep-cr option and would pass it to git-mailsplit. Since e80d4cb (git-am: Add am.keepcr and --no-keep-cr to override it, 2010-02-27), git-am.sh supported the am.keepcr config setting, which controls whether --keep-cr is on by default. Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 8e978395b2..e34bc517ba 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -502,7 +502,7 @@ done: * Splits out individual email patches from `paths`, where each path is either * a mbox file or a Maildir. Returns 0 on success, -1 on failure. */ -static int split_mail_mbox(struct am_state *state, const char **paths) +static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf last = STRBUF_INIT; @@ -512,6 +512,8 @@ static int split_mail_mbox(struct am_state *state, const char **paths) argv_array_pushf(&cp.args, "-d%d", state->prec); argv_array_pushf(&cp.args, "-o%s", state->dir); argv_array_push(&cp.args, "-b"); + if (keep_cr) + argv_array_push(&cp.args, "--keep-cr"); argv_array_push(&cp.args, "--"); argv_array_pushv(&cp.args, paths); @@ -536,14 +538,22 @@ static int split_mail_mbox(struct am_state *state, const char **paths) * state->cur will be set to the index of the first mail, and state->last will * be set to the index of the last mail. * + * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1 + * to disable this behavior, -1 to use the default configured setting. + * * Returns 0 on success, -1 on failure. */ static int split_mail(struct am_state *state, enum patch_format patch_format, - const char **paths) + const char **paths, int keep_cr) { + if (keep_cr < 0) { + keep_cr = 0; + git_config_get_bool("am.keepcr", &keep_cr); + } + switch (patch_format) { case PATCH_FORMAT_MBOX: - return split_mail_mbox(state, paths); + return split_mail_mbox(state, paths, keep_cr); default: die("BUG: invalid patch_format"); } @@ -554,7 +564,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, * Setup a new am session for applying patches */ static void am_setup(struct am_state *state, enum patch_format patch_format, - const char **paths) + const char **paths, int keep_cr) { unsigned char curr_head[GIT_SHA1_RAWSZ]; const char *str; @@ -570,7 +580,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir); - if (split_mail(state, patch_format, paths) < 0) { + if (split_mail(state, patch_format, paths, keep_cr) < 0) { am_destroy(state); die(_("Failed to split patches.")); } @@ -1511,6 +1521,7 @@ enum resume_mode { int cmd_am(int argc, const char **argv, const char *prefix) { struct am_state state; + int keep_cr = -1; int patch_format = PATCH_FORMAT_UNKNOWN; enum resume_mode resume = RESUME_FALSE; @@ -1534,6 +1545,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH), OPT_BOOL('m', "message-id", &state.message_id, N_("pass -m flag to git-mailinfo")), + { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL, + N_("pass --keep-cr flag to git-mailsplit for mbox format"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, + { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL, + N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), @@ -1631,7 +1648,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i])); } - am_setup(&state, patch_format, paths.argv); + am_setup(&state, patch_format, paths.argv, keep_cr); argv_array_clear(&paths); } -- cgit v1.2.3 From 9b646617b8b4894490a549e4db95c1303f40bd25 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:49 +0800 Subject: builtin-am: implement --[no-]scissors Since 017678b (am/mailinfo: Disable scissors processing by default, 2009-08-26), git-am supported the --[no-]scissors option, passing it to git-mailinfo. Re-implement support for this option in builtin/am.c. Since the default setting of --scissors in git-mailinfo can be configured with mailinfo.scissors (and perhaps through other settings in the future), to be safe we make an explicit distinction between SCISSORS_UNSET, SCISSORS_TRUE and SCISSORS_FALSE. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index e34bc517ba..727cfb8f96 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -74,6 +74,12 @@ enum keep_type { KEEP_NON_PATCH /* pass -b flag to git-mailinfo */ }; +enum scissors_type { + SCISSORS_UNSET = -1, + SCISSORS_FALSE = 0, /* pass --no-scissors to git-mailinfo */ + SCISSORS_TRUE /* pass --scissors to git-mailinfo */ +}; + struct am_state { /* state directory path */ char *dir; @@ -99,6 +105,7 @@ struct am_state { int utf8; int keep; /* enum keep_type */ int message_id; + int scissors; /* enum scissors_type */ const char *resolvemsg; int rebasing; }; @@ -119,6 +126,8 @@ static void am_state_init(struct am_state *state, const char *dir) state->utf8 = 1; git_config_get_bool("am.messageid", &state->message_id); + + state->scissors = SCISSORS_UNSET; } /** @@ -394,6 +403,14 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "messageid", 1); state->message_id = !strcmp(sb.buf, "t"); + read_state_file(&sb, state, "scissors", 1); + if (!strcmp(sb.buf, "t")) + state->scissors = SCISSORS_TRUE; + else if (!strcmp(sb.buf, "f")) + state->scissors = SCISSORS_FALSE; + else + state->scissors = SCISSORS_UNSET; + state->rebasing = !!file_exists(am_path(state, "rebasing")); strbuf_release(&sb); @@ -614,6 +631,22 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f"); + switch (state->scissors) { + case SCISSORS_UNSET: + str = ""; + break; + case SCISSORS_FALSE: + str = "f"; + break; + case SCISSORS_TRUE: + str = "t"; + break; + default: + die("BUG: invalid value for state->scissors"); + } + + write_file(am_path(state, "scissors"), 1, "%s", str); + if (state->rebasing) write_file(am_path(state, "rebasing"), 1, "%s", ""); else @@ -798,6 +831,19 @@ static int parse_mail(struct am_state *state, const char *mail) if (state->message_id) argv_array_push(&cp.args, "-m"); + switch (state->scissors) { + case SCISSORS_UNSET: + break; + case SCISSORS_FALSE: + argv_array_push(&cp.args, "--no-scissors"); + break; + case SCISSORS_TRUE: + argv_array_push(&cp.args, "--scissors"); + break; + default: + die("BUG: invalid value for state->scissors"); + } + argv_array_push(&cp.args, am_path(state, "msg")); argv_array_push(&cp.args, am_path(state, "patch")); @@ -1551,6 +1597,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL, N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + OPT_BOOL('c', "scissors", &state.scissors, + N_("strip everything before a scissors line")), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), -- cgit v1.2.3 From 257e8cecc13b323c7b5c3fb249955b3ee8f4af3d Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:50 +0800 Subject: builtin-am: pass git-apply's options to git-apply git-am.sh recognizes some of git-apply's options, and would pass them to git-apply: * --whitespace, since 8c31cb8 (git-am: --whitespace=x option., 2006-02-28) * -C, since 67dad68 (add -C[NUM] to git-am, 2007-02-08) * -p, since 2092a1f (Teach git-am to pass -p option down to git-apply, 2007-02-11) * --directory, since b47dfe9 (git-am: add --directory= option, 2009-01-11) * --reject, since b80da42 (git-am: implement --reject option passed to git-apply, 2009-01-23) * --ignore-space-change, --ignore-whitespace, since 86c91f9 (git apply: option to ignore whitespace differences, 2009-08-04) * --exclude, since 77e9e49 (am: pass exclude down to apply, 2011-08-03) * --include, since 58725ef (am: support --include option, 2012-03-28) * --reject, since b80da42 (git-am: implement --reject option passed to git-apply, 2009-01-23) Re-implement support for these options in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 727cfb8f96..f842f69d36 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -106,6 +106,7 @@ struct am_state { int keep; /* enum keep_type */ int message_id; int scissors; /* enum scissors_type */ + struct argv_array git_apply_opts; const char *resolvemsg; int rebasing; }; @@ -128,6 +129,8 @@ static void am_state_init(struct am_state *state, const char *dir) git_config_get_bool("am.messageid", &state->message_id); state->scissors = SCISSORS_UNSET; + + argv_array_init(&state->git_apply_opts); } /** @@ -140,6 +143,7 @@ static void am_state_release(struct am_state *state) free(state->author_email); free(state->author_date); free(state->msg); + argv_array_clear(&state->git_apply_opts); } /** @@ -411,6 +415,11 @@ static void am_load(struct am_state *state) else state->scissors = SCISSORS_UNSET; + read_state_file(&sb, state, "apply-opt", 1); + argv_array_clear(&state->git_apply_opts); + if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0) + die(_("could not parse %s"), am_path(state, "apply-opt")); + state->rebasing = !!file_exists(am_path(state, "rebasing")); strbuf_release(&sb); @@ -585,6 +594,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, { unsigned char curr_head[GIT_SHA1_RAWSZ]; const char *str; + struct strbuf sb = STRBUF_INIT; if (!patch_format) patch_format = detect_patch_format(paths); @@ -647,6 +657,9 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "scissors"), 1, "%s", str); + sq_quote_argv(&sb, state->git_apply_opts.argv, 0); + write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf); + if (state->rebasing) write_file(am_path(state, "rebasing"), 1, "%s", ""); else @@ -671,6 +684,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_file(am_path(state, "next"), 1, "%d", state->cur); write_file(am_path(state, "last"), 1, "%d", state->last); + + strbuf_release(&sb); } /** @@ -1058,6 +1073,8 @@ static int run_apply(const struct am_state *state, const char *index_file) argv_array_push(&cp.args, "apply"); + argv_array_pushv(&cp.args, state->git_apply_opts.argv); + if (index_file) argv_array_push(&cp.args, "--cached"); else @@ -1084,6 +1101,7 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f cp.git_cmd = 1; argv_array_push(&cp.args, "apply"); + argv_array_pushv(&cp.args, state->git_apply_opts.argv); argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); argv_array_push(&cp.args, am_path(state, "patch")); @@ -1599,9 +1617,36 @@ int cmd_am(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, OPT_BOOL('c', "scissors", &state.scissors, N_("strip everything before a scissors line")), + OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"), + N_("pass it through git-apply"), + 0), + OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"), + N_("pass it through git-apply"), + 0), OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), + OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL, + N_("pass it through git-apply"), + PARSE_OPT_NOARG), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), OPT_CMDMODE(0, "continue", &resume, -- cgit v1.2.3 From f07adb62f292eec0d10fd10b0812800afb03363c Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:51 +0800 Subject: builtin-am: implement --ignore-date Since a79ec62 (git-am: Add --ignore-date option, 2009-01-24), git-am.sh supported the --ignore-date option, and would use the current timestamp instead of the one provided in the patch if the option was set. Re-implement this option in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index f842f69d36..84d3e0519e 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -108,6 +108,7 @@ struct am_state { int scissors; /* enum scissors_type */ struct argv_array git_apply_opts; const char *resolvemsg; + int ignore_date; int rebasing; }; @@ -1217,7 +1218,8 @@ static void do_commit(const struct am_state *state) } author = fmt_ident(state->author_name, state->author_email, - state->author_date, IDENT_STRICT); + state->ignore_date ? NULL : state->author_date, + IDENT_STRICT); if (commit_tree(state->msg, state->msg_len, tree, parents, commit, author, NULL)) @@ -1661,6 +1663,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), + OPT_BOOL(0, "ignore-date", &state.ignore_date, + N_("use current timestamp for author date")), OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, N_("(internal use for git-rebase)")), OPT_END() -- cgit v1.2.3 From 0cd4bcba679041274f9f5c381737f6f92b03b1b6 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:52 +0800 Subject: builtin-am: implement --committer-date-is-author-date Since 3f01ad6 (am: Add --committer-date-is-author-date option, 2009-01-22), git-am.sh implemented the --committer-date-is-author-date option, which tells git-am to use the timestamp recorded in the email message as both author and committer date. Re-implement this option in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 84d3e0519e..1561580de4 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -108,6 +108,7 @@ struct am_state { int scissors; /* enum scissors_type */ struct argv_array git_apply_opts; const char *resolvemsg; + int committer_date_is_author_date; int ignore_date; int rebasing; }; @@ -1221,6 +1222,10 @@ static void do_commit(const struct am_state *state) state->ignore_date ? NULL : state->author_date, IDENT_STRICT); + if (state->committer_date_is_author_date) + setenv("GIT_COMMITTER_DATE", + state->ignore_date ? "" : state->author_date, 1); + if (commit_tree(state->msg, state->msg_len, tree, parents, commit, author, NULL)) die(_("failed to write commit object")); @@ -1663,6 +1668,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), + OPT_BOOL(0, "committer-date-is-author-date", + &state.committer_date_is_author_date, + N_("lie about committer date")), OPT_BOOL(0, "ignore-date", &state.ignore_date, N_("use current timestamp for author date")), OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, -- cgit v1.2.3 From 7e35dacbe392f0d5faf3c75e8964ae96aa4636a7 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:53 +0800 Subject: builtin-am: implement -S/--gpg-sign, commit.gpgsign Since 3b4e395 (am: add the --gpg-sign option, 2014-02-01), git-am.sh supported the --gpg-sign option, and would pass it to git-commit-tree, thus GPG-signing the commit object. Re-implement this option in builtin/am.c. git-commit-tree would also sign the commit by default if the commit.gpgsign setting is true. Since we do not run commit-tree, we re-implement this behavior by handling the commit.gpgsign setting ourselves. Helped-by: Stefan Beller Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 1561580de4..18611fa0c3 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -110,6 +110,7 @@ struct am_state { const char *resolvemsg; int committer_date_is_author_date; int ignore_date; + const char *sign_commit; int rebasing; }; @@ -119,6 +120,8 @@ struct am_state { */ static void am_state_init(struct am_state *state, const char *dir) { + int gpgsign; + memset(state, 0, sizeof(*state)); assert(dir); @@ -133,6 +136,9 @@ static void am_state_init(struct am_state *state, const char *dir) state->scissors = SCISSORS_UNSET; argv_array_init(&state->git_apply_opts); + + if (!git_config_get_bool("commit.gpgsign", &gpgsign)) + state->sign_commit = gpgsign ? "" : NULL; } /** @@ -1227,7 +1233,7 @@ static void do_commit(const struct am_state *state) state->ignore_date ? "" : state->author_date, 1); if (commit_tree(state->msg, state->msg_len, tree, parents, commit, - author, NULL)) + author, state->sign_commit)) die(_("failed to write commit object")); reflog_msg = getenv("GIT_REFLOG_ACTION"); @@ -1673,6 +1679,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("lie about committer date")), OPT_BOOL(0, "ignore-date", &state.ignore_date, N_("use current timestamp for author date")), + { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, N_("(internal use for git-rebase)")), OPT_END() -- cgit v1.2.3 From 13b97ea5f0bec8f529e8b819a24502023bfbacba Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:54 +0800 Subject: builtin-am: invoke post-rewrite hook Since 96e1948 (rebase: invoke post-rewrite hook, 2010-03-12), git-am.sh will invoke the post-rewrite hook after it successfully finishes applying all the queued patches. To do this, when parsing a mail to extract its patch and metadata, in --rebasing mode git-am.sh will also store the original commit ID in the $state_dir/original-commit file. Once it applies and commits the patch, the original commit ID, and the new commit ID, will be appended to the $state_dir/rewritten file. Once all of the queued mail have been processed, git-am.sh will then invoke the post-rewrite hook with the contents of the $state_dir/rewritten file. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 18611fa0c3..dbec9fc67c 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -95,6 +95,9 @@ struct am_state { char *msg; size_t msg_len; + /* when --rebasing, records the original commit the patch came from */ + unsigned char orig_commit[GIT_SHA1_RAWSZ]; + /* number of digits in patch filename */ int prec; @@ -392,6 +395,11 @@ static void am_load(struct am_state *state) read_commit_msg(state); + if (read_state_file(&sb, state, "original-commit", 1) < 0) + hashclr(state->orig_commit); + else if (get_sha1_hex(sb.buf, state->orig_commit) < 0) + die(_("could not parse %s"), am_path(state, "original-commit")); + read_state_file(&sb, state, "threeway", 1); state->threeway = !strcmp(sb.buf, "t"); @@ -446,6 +454,30 @@ static void am_destroy(const struct am_state *state) strbuf_release(&sb); } +/** + * Runs post-rewrite hook. Returns it exit code. + */ +static int run_post_rewrite_hook(const struct am_state *state) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *hook = find_hook("post-rewrite"); + int ret; + + if (!hook) + return 0; + + argv_array_push(&cp.args, hook); + argv_array_push(&cp.args, "rebase"); + + cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); + cp.stdout_to_stderr = 1; + + ret = run_command(&cp); + + close(cp.in); + return ret; +} + /** * Determines if the file looks like a piece of RFC2822 mail by grabbing all * non-indented lines and checking if they look like they begin with valid @@ -720,6 +752,9 @@ static void am_next(struct am_state *state) unlink(am_path(state, "author-script")); unlink(am_path(state, "final-commit")); + hashclr(state->orig_commit); + unlink(am_path(state, "original-commit")); + if (!get_sha1("HEAD", head)) write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head)); else @@ -1038,6 +1073,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm * directly. This is used in --rebasing mode to bypass git-mailinfo's munging * of patches. * + * state->orig_commit will be set to the original commit ID. + * * Will always return 0 as the patch should never be skipped. */ static int parse_mail_rebase(struct am_state *state, const char *mail) @@ -1054,6 +1091,10 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) write_commit_patch(state, commit); + hashcpy(state->orig_commit, commit_sha1); + write_file(am_path(state, "original-commit"), 1, "%s", + sha1_to_hex(commit_sha1)); + return 0; } @@ -1245,6 +1286,15 @@ static void do_commit(const struct am_state *state) update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR); + if (state->rebasing) { + FILE *fp = xfopen(am_path(state, "rewritten"), "a"); + + assert(!is_null_sha1(state->orig_commit)); + fprintf(fp, "%s ", sha1_to_hex(state->orig_commit)); + fprintf(fp, "%s\n", sha1_to_hex(commit)); + fclose(fp); + } + strbuf_release(&sb); } @@ -1353,6 +1403,11 @@ next: am_next(state); } + if (!is_empty_file(am_path(state, "rewritten"))) { + assert(state->rebasing); + run_post_rewrite_hook(state); + } + /* * In rebasing mode, it's up to the caller to take care of * housekeeping. -- cgit v1.2.3 From 88b291fe9db645366853fc759d497342c130fd35 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:55 +0800 Subject: builtin-am: support automatic notes copying Since eb2151b (rebase: support automatic notes copying, 2010-03-12), git-am.sh supported automatic notes copying in --rebasing mode by invoking "git notes copy" once it has finished applying all the patches. Re-implement this feature in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index dbec9fc67c..7d7f91df2c 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -23,6 +23,7 @@ #include "merge-recursive.h" #include "revision.h" #include "log-tree.h" +#include "notes-utils.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -478,6 +479,64 @@ static int run_post_rewrite_hook(const struct am_state *state) return ret; } +/** + * Reads the state directory's "rewritten" file, and copies notes from the old + * commits listed in the file to their rewritten commits. + * + * Returns 0 on success, -1 on failure. + */ +static int copy_notes_for_rebase(const struct am_state *state) +{ + struct notes_rewrite_cfg *c; + struct strbuf sb = STRBUF_INIT; + const char *invalid_line = _("Malformed input line: '%s'."); + const char *msg = "Notes added by 'git rebase'"; + FILE *fp; + int ret = 0; + + assert(state->rebasing); + + c = init_copy_notes_for_rewrite("rebase"); + if (!c) + return 0; + + fp = xfopen(am_path(state, "rewritten"), "r"); + + while (!strbuf_getline(&sb, fp, '\n')) { + unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ]; + + if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (get_sha1_hex(sb.buf, from_obj)) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (sb.buf[GIT_SHA1_HEXSZ] != ' ') { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) { + ret = error(invalid_line, sb.buf); + goto finish; + } + + if (copy_note_for_rewrite(c, from_obj, to_obj)) + ret = error(_("Failed to copy notes from '%s' to '%s'"), + sha1_to_hex(from_obj), sha1_to_hex(to_obj)); + } + +finish: + finish_copy_notes_for_rewrite(c, msg); + fclose(fp); + strbuf_release(&sb); + return ret; +} + /** * Determines if the file looks like a piece of RFC2822 mail by grabbing all * non-indented lines and checking if they look like they begin with valid @@ -1405,6 +1464,7 @@ next: if (!is_empty_file(am_path(state, "rewritten"))) { assert(state->rebasing); + copy_notes_for_rebase(state); run_post_rewrite_hook(state); } -- cgit v1.2.3 From b8803d8f8c987b9867385a033aab71a7cb967553 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:56 +0800 Subject: builtin-am: invoke applypatch-msg hook Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh will invoke the applypatch-msg hooks just after extracting the patch message. If the applypatch-msg hook exits with a non-zero status, git-am.sh abort before even applying the patch to the index. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 7d7f91df2c..f0e3aab9af 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -455,6 +455,27 @@ static void am_destroy(const struct am_state *state) strbuf_release(&sb); } +/** + * Runs applypatch-msg hook. Returns its exit code. + */ +static int run_applypatch_msg_hook(struct am_state *state) +{ + int ret; + + assert(state->msg); + ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); + + if (!ret) { + free(state->msg); + state->msg = NULL; + if (read_commit_msg(state) < 0) + die(_("'%s' was deleted by the applypatch-msg hook"), + am_path(state, "final-commit")); + } + + return ret; +} + /** * Runs post-rewrite hook. Returns it exit code. */ @@ -1420,6 +1441,9 @@ static void am_run(struct am_state *state, int resume) write_commit_msg(state); } + if (run_applypatch_msg_hook(state)) + exit(1); + say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); apply_status = run_apply(state, NULL); -- cgit v1.2.3 From 6c24c5c0a5f761f698f61a7a2c84d26a3589ef6b Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:57 +0800 Subject: builtin-am: invoke pre-applypatch hook Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sg will invoke the pre-applypatch hook after applying the patch to the index, but before a commit is made. Should the hook exit with a non-zero status, git am will exit. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index f0e3aab9af..7a7da943a3 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1334,6 +1334,9 @@ static void do_commit(const struct am_state *state) const char *reflog_msg, *author; struct strbuf sb = STRBUF_INIT; + if (run_hook_le(NULL, "pre-applypatch", NULL)) + exit(1); + if (write_cache_as_tree(tree, 0, NULL)) die(_("git write-tree failed to write a tree")); -- cgit v1.2.3 From 7088f8078a89d62733a12878f944d48365716f53 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:58 +0800 Subject: builtin-am: invoke post-applypatch hook Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh will invoke the post-applypatch hook after the patch is applied and a commit is made. The exit code of the hook is ignored. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 7a7da943a3..c313e58dcd 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1378,6 +1378,8 @@ static void do_commit(const struct am_state *state) fclose(fp); } + run_hook_le(NULL, "post-applypatch", NULL); + strbuf_release(&sb); } -- cgit v1.2.3 From f1cb96d687688afba0ae08d1c6fdd58c9fdbec1e Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:51:59 +0800 Subject: builtin-am: rerere support git-am.sh will call git-rerere at the following events: * "git rerere" when a three-way merge fails to record the conflicted automerge results. Since 8389b52 (git-rerere: reuse recorded resolve., 2006-01-28) * Since cb6020b (Teach --[no-]rerere-autoupdate option to merge, revert and friends, 2009-12-04), git-am.sh supports the --[no-]rerere-autoupdate option as well, and would pass it to git-rerere. * "git rerere" when --resolved, to record the hand resolution. Since f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or aborted rebase/am, 2006-12-08) * "git rerere clear" when --skip-ing. Since f131dd4 (rerere: record (or avoid misrecording) resolved, skipped or aborted rebase/am, 2006-12-08) * "git rerere clear" when --abort-ing. Since 3e5057a (git am --abort, 2008-07-16) Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index c313e58dcd..33d1f24d07 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -24,6 +24,7 @@ #include "revision.h" #include "log-tree.h" #include "notes-utils.h" +#include "rerere.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -114,6 +115,7 @@ struct am_state { const char *resolvemsg; int committer_date_is_author_date; int ignore_date; + int allow_rerere_autoupdate; const char *sign_commit; int rebasing; }; @@ -1312,6 +1314,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa o.verbosity = 0; if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) { + rerere(state->allow_rerere_autoupdate); free(his_tree_name); return error(_("Failed to merge in the changes.")); } @@ -1531,6 +1534,8 @@ static void am_resolve(struct am_state *state) die_user_resolve(state); } + rerere(0); + do_commit(state); am_next(state); @@ -1630,6 +1635,21 @@ static int clean_index(const unsigned char *head, const unsigned char *remote) return 0; } +/** + * Resets rerere's merge resolution metadata. + */ +static void am_rerere_clear(void) +{ + struct string_list merge_rr = STRING_LIST_INIT_DUP; + int fd = setup_rerere(&merge_rr, 0); + + if (fd < 0) + return; + + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); +} + /** * Resume the current am session by skipping the current patch. */ @@ -1637,6 +1657,8 @@ static void am_skip(struct am_state *state) { unsigned char head[GIT_SHA1_RAWSZ]; + am_rerere_clear(); + if (get_sha1("HEAD", head)) hashcpy(head, EMPTY_TREE_SHA1_BIN); @@ -1694,6 +1716,8 @@ static void am_abort(struct am_state *state) return; } + am_rerere_clear(); + curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL); has_curr_head = !is_null_sha1(curr_head); if (!has_curr_head) @@ -1823,6 +1847,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("lie about committer date")), OPT_BOOL(0, "ignore-date", &state.ignore_date, N_("use current timestamp for author date")), + OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate), { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, -- cgit v1.2.3 From 5ae41c79b81a5c73939749bee19c1d1075cc0cdf Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:00 +0800 Subject: builtin-am: support and auto-detect StGit patches Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27), git-am.sh supported converting StGit patches into RFC2822 mail patches that can be parsed with git-mailinfo. Implement this by introducing two functions in builtin/am.c: stgit_patch_to_mail() and split_mail_conv(). stgit_patch_to_mail() is a callback function for split_mail_conv(), and contains the logic for converting an StGit patch into an RFC2822 mail patch. split_mail_conv() implements the logic to go through each file in the `paths` list, reading from stdin where specified, and calls the callback function to write the converted patch to the corresponding output file in the state directory. This interface should be generic enough to support other foreign patch formats in the future. Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches. Re-implement this in builtin/am.c. Helped-by: Eric Sunshine Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 33d1f24d07..d82d07edaa 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -65,9 +65,22 @@ static int linelen(const char *msg) return strchrnul(msg, '\n') - msg; } +/** + * Returns true if `str` consists of only whitespace, false otherwise. + */ +static int str_isspace(const char *str) +{ + for (; *str; str++) + if (!isspace(*str)) + return 0; + + return 1; +} + enum patch_format { PATCH_FORMAT_UNKNOWN = 0, - PATCH_FORMAT_MBOX + PATCH_FORMAT_MBOX, + PATCH_FORMAT_STGIT }; enum keep_type { @@ -610,6 +623,8 @@ static int detect_patch_format(const char **paths) { enum patch_format ret = PATCH_FORMAT_UNKNOWN; struct strbuf l1 = STRBUF_INIT; + struct strbuf l2 = STRBUF_INIT; + struct strbuf l3 = STRBUF_INIT; FILE *fp; /* @@ -635,6 +650,23 @@ static int detect_patch_format(const char **paths) goto done; } + strbuf_reset(&l2); + strbuf_getline_crlf(&l2, fp); + strbuf_reset(&l3); + strbuf_getline_crlf(&l3, fp); + + /* + * If the second line is empty and the third is a From, Author or Date + * entry, this is likely an StGit patch. + */ + if (l1.len && !l2.len && + (starts_with(l3.buf, "From:") || + starts_with(l3.buf, "Author:") || + starts_with(l3.buf, "Date:"))) { + ret = PATCH_FORMAT_STGIT; + goto done; + } + if (l1.len && is_mail(fp)) { ret = PATCH_FORMAT_MBOX; goto done; @@ -674,6 +706,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_ return 0; } +/** + * Callback signature for split_mail_conv(). The foreign patch should be + * read from `in`, and the converted patch (in RFC2822 mail format) should be + * written to `out`. Return 0 on success, or -1 on failure. + */ +typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr); + +/** + * Calls `fn` for each file in `paths` to convert the foreign patch to the + * RFC2822 mail format suitable for parsing with git-mailinfo. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail_conv(mail_conv_fn fn, struct am_state *state, + const char **paths, int keep_cr) +{ + static const char *stdin_only[] = {"-", NULL}; + int i; + + if (!*paths) + paths = stdin_only; + + for (i = 0; *paths; paths++, i++) { + FILE *in, *out; + const char *mail; + int ret; + + if (!strcmp(*paths, "-")) + in = stdin; + else + in = fopen(*paths, "r"); + + if (!in) + return error(_("could not open '%s' for reading: %s"), + *paths, strerror(errno)); + + mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1); + + out = fopen(mail, "w"); + if (!out) + return error(_("could not open '%s' for writing: %s"), + mail, strerror(errno)); + + ret = fn(out, in, keep_cr); + + fclose(out); + fclose(in); + + if (ret) + return error(_("could not parse patch '%s'"), *paths); + } + + state->cur = 1; + state->last = i; + return 0; +} + +/** + * A split_mail_conv() callback that converts an StGit patch to an RFC2822 + * message suitable for parsing with git-mailinfo. + */ +static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr) +{ + struct strbuf sb = STRBUF_INIT; + int subject_printed = 0; + + while (!strbuf_getline(&sb, in, '\n')) { + const char *str; + + if (str_isspace(sb.buf)) + continue; + else if (skip_prefix(sb.buf, "Author:", &str)) + fprintf(out, "From:%s\n", str); + else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date")) + fprintf(out, "%s\n", sb.buf); + else if (!subject_printed) { + fprintf(out, "Subject: %s\n", sb.buf); + subject_printed = 1; + } else { + fprintf(out, "\n%s\n", sb.buf); + break; + } + } + + strbuf_reset(&sb); + while (strbuf_fread(&sb, 8192, in) > 0) { + fwrite(sb.buf, 1, sb.len, out); + strbuf_reset(&sb); + } + + strbuf_release(&sb); + return 0; +} + /** * Splits a list of files/directories into individual email patches. Each path * in `paths` must be a file/directory that is formatted according to @@ -702,6 +828,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, switch (patch_format) { case PATCH_FORMAT_MBOX: return split_mail_mbox(state, paths, keep_cr); + case PATCH_FORMAT_STGIT: + return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr); default: die("BUG: invalid patch_format"); } @@ -1750,6 +1878,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int if (!strcmp(arg, "mbox")) *opt_value = PATCH_FORMAT_MBOX; + else if (!strcmp(arg, "stgit")) + *opt_value = PATCH_FORMAT_STGIT; else return error(_("Invalid value for --patch-format: %s"), arg); return 0; -- cgit v1.2.3 From 336108c1563c23e1af5b7b7e6fbe52f511c21748 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:01 +0800 Subject: builtin-am: support and auto-detect StGit series files Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27), git-am.sh is able to read a single StGit series file and, for each StGit patch listed in the file, convert the StGit patch into a RFC2822 mail patch suitable for parsing with git-mailinfo, and queue them in the state directory for applying. Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to auto-detect StGit series files by checking to see if the file starts with the string: # This series applies on GIT commit Re-implement the above in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index d82d07edaa..3c2ec15570 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -80,7 +80,8 @@ static int str_isspace(const char *str) enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX, - PATCH_FORMAT_STGIT + PATCH_FORMAT_STGIT, + PATCH_FORMAT_STGIT_SERIES }; enum keep_type { @@ -650,6 +651,11 @@ static int detect_patch_format(const char **paths) goto done; } + if (starts_with(l1.buf, "# This series applies on GIT commit")) { + ret = PATCH_FORMAT_STGIT_SERIES; + goto done; + } + strbuf_reset(&l2); strbuf_getline_crlf(&l2, fp); strbuf_reset(&l3); @@ -800,6 +806,53 @@ static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr) return 0; } +/** + * This function only supports a single StGit series file in `paths`. + * + * Given an StGit series file, converts the StGit patches in the series into + * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in + * the state directory. + * + * Returns 0 on success, -1 on failure. + */ +static int split_mail_stgit_series(struct am_state *state, const char **paths, + int keep_cr) +{ + const char *series_dir; + char *series_dir_buf; + FILE *fp; + struct argv_array patches = ARGV_ARRAY_INIT; + struct strbuf sb = STRBUF_INIT; + int ret; + + if (!paths[0] || paths[1]) + return error(_("Only one StGIT patch series can be applied at once")); + + series_dir_buf = xstrdup(*paths); + series_dir = dirname(series_dir_buf); + + fp = fopen(*paths, "r"); + if (!fp) + return error(_("could not open '%s' for reading: %s"), *paths, + strerror(errno)); + + while (!strbuf_getline(&sb, fp, '\n')) { + if (*sb.buf == '#') + continue; /* skip comment lines */ + + argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf)); + } + + fclose(fp); + strbuf_release(&sb); + free(series_dir_buf); + + ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr); + + argv_array_clear(&patches); + return ret; +} + /** * Splits a list of files/directories into individual email patches. Each path * in `paths` must be a file/directory that is formatted according to @@ -830,6 +883,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, return split_mail_mbox(state, paths, keep_cr); case PATCH_FORMAT_STGIT: return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr); + case PATCH_FORMAT_STGIT_SERIES: + return split_mail_stgit_series(state, paths, keep_cr); default: die("BUG: invalid patch_format"); } @@ -1880,6 +1935,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int *opt_value = PATCH_FORMAT_MBOX; else if (!strcmp(arg, "stgit")) *opt_value = PATCH_FORMAT_STGIT; + else if (!strcmp(arg, "stgit-series")) + *opt_value = PATCH_FORMAT_STGIT_SERIES; else return error(_("Invalid value for --patch-format: %s"), arg); return 0; -- cgit v1.2.3 From 94cd175cff196d2571bc2e17ad8e9f47ef4b3911 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:02 +0800 Subject: builtin-am: support and auto-detect mercurial patches Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29), git-am.sh could convert mercurial patches to an RFC2822 mail patch suitable for parsing with git-mailinfo, and queue them in the state directory for application. Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh was able to auto-detect mercurial patches by checking if the file begins with the line: # HG changeset patch Re-implement the above in builtin/am.c. Helped-by: Stefan Beller Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 3c2ec15570..98c10a0b71 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -81,7 +81,8 @@ enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX, PATCH_FORMAT_STGIT, - PATCH_FORMAT_STGIT_SERIES + PATCH_FORMAT_STGIT_SERIES, + PATCH_FORMAT_HG }; enum keep_type { @@ -656,6 +657,11 @@ static int detect_patch_format(const char **paths) goto done; } + if (!strcmp(l1.buf, "# HG changeset patch")) { + ret = PATCH_FORMAT_HG; + goto done; + } + strbuf_reset(&l2); strbuf_getline_crlf(&l2, fp); strbuf_reset(&l3); @@ -853,6 +859,68 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, return ret; } +/** + * A split_patches_conv() callback that converts a mercurial patch to a RFC2822 + * message suitable for parsing with git-mailinfo. + */ +static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) +{ + struct strbuf sb = STRBUF_INIT; + + while (!strbuf_getline(&sb, in, '\n')) { + const char *str; + + if (skip_prefix(sb.buf, "# User ", &str)) + fprintf(out, "From: %s\n", str); + else if (skip_prefix(sb.buf, "# Date ", &str)) { + unsigned long timestamp; + long tz, tz2; + char *end; + + errno = 0; + timestamp = strtoul(str, &end, 10); + if (errno) + return error(_("invalid timestamp")); + + if (!skip_prefix(end, " ", &str)) + return error(_("invalid Date line")); + + errno = 0; + tz = strtol(str, &end, 10); + if (errno) + return error(_("invalid timezone offset")); + + if (*end) + return error(_("invalid Date line")); + + /* + * mercurial's timezone is in seconds west of UTC, + * however git's timezone is in hours + minutes east of + * UTC. Convert it. + */ + tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60; + if (tz > 0) + tz2 = -tz2; + + fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822))); + } else if (starts_with(sb.buf, "# ")) { + continue; + } else { + fprintf(out, "\n%s\n", sb.buf); + break; + } + } + + strbuf_reset(&sb); + while (strbuf_fread(&sb, 8192, in) > 0) { + fwrite(sb.buf, 1, sb.len, out); + strbuf_reset(&sb); + } + + strbuf_release(&sb); + return 0; +} + /** * Splits a list of files/directories into individual email patches. Each path * in `paths` must be a file/directory that is formatted according to @@ -885,6 +953,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr); case PATCH_FORMAT_STGIT_SERIES: return split_mail_stgit_series(state, paths, keep_cr); + case PATCH_FORMAT_HG: + return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr); default: die("BUG: invalid patch_format"); } @@ -1937,6 +2007,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int *opt_value = PATCH_FORMAT_STGIT; else if (!strcmp(arg, "stgit-series")) *opt_value = PATCH_FORMAT_STGIT_SERIES; + else if (!strcmp(arg, "hg")) + *opt_value = PATCH_FORMAT_HG; else return error(_("Invalid value for --patch-format: %s"), arg); return 0; -- cgit v1.2.3 From 7ff26832535a83bd8d75beefbf822d965e066961 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:03 +0800 Subject: builtin-am: implement -i/--interactive Since d1c5f2a (Add git-am, applymbox replacement., 2005-10-07), git-am.sh supported the --interactive mode. After parsing the patch mail and extracting the patch, commit message and authorship info, an interactive session will begin that allows the user to choose between: * applying the patch * applying the patch and all subsequent patches (by disabling interactive mode in subsequent patches) * skipping the patch * editing the commit message Since f89ad67 (Add [v]iew patch in git-am interactive., 2005-10-25), git-am.sh --interactive also supported viewing the patch to be applied. When --resolved-ing in --interactive mode, we need to take care to update the patch with the contents of the index, such that the correct patch will be displayed when the patch is viewed in interactive mode. Re-implement the above in builtin/am.c Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/builtin/am.c b/builtin/am.c index 98c10a0b71..589199f417 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -25,6 +25,7 @@ #include "log-tree.h" #include "notes-utils.h" #include "rerere.h" +#include "prompt.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -119,6 +120,7 @@ struct am_state { int prec; /* various operating modes and command line options */ + int interactive; int threeway; int quiet; int signoff; @@ -1171,7 +1173,7 @@ static void NORETURN die_user_resolve(const struct am_state *state) if (state->resolvemsg) { printf_ln("%s", state->resolvemsg); } else { - const char *cmdline = "git am"; + const char *cmdline = state->interactive ? "git am -i" : "git am"; printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline); printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline); @@ -1403,6 +1405,36 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm log_tree_commit(&rev_info, commit); } +/** + * Writes the diff of the index against HEAD as a patch to the state + * directory's "patch" file. + */ +static void write_index_patch(const struct am_state *state) +{ + struct tree *tree; + unsigned char head[GIT_SHA1_RAWSZ]; + struct rev_info rev_info; + FILE *fp; + + if (!get_sha1_tree("HEAD", head)) + tree = lookup_tree(head); + else + tree = lookup_tree(EMPTY_TREE_SHA1_BIN); + + fp = xfopen(am_path(state, "patch"), "w"); + init_revisions(&rev_info, NULL); + rev_info.diff = 1; + rev_info.disable_stdin = 1; + rev_info.no_commit_id = 1; + rev_info.diffopt.output_format = DIFF_FORMAT_PATCH; + rev_info.diffopt.use_color = 0; + rev_info.diffopt.file = fp; + rev_info.diffopt.close_file = 1; + add_pending_object(&rev_info, &tree->object, ""); + diff_setup_done(&rev_info.diffopt); + run_diff_index(&rev_info, 1); +} + /** * Like parse_mail(), but parses the mail by looking up its commit ID * directly. This is used in --rebasing mode to bypass git-mailinfo's munging @@ -1654,6 +1686,65 @@ static void validate_resume_state(const struct am_state *state) am_path(state, "author-script")); } +/** + * Interactively prompt the user on whether the current patch should be + * applied. + * + * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to + * skip it. + */ +static int do_interactive(struct am_state *state) +{ + assert(state->msg); + + if (!isatty(0)) + die(_("cannot be interactive without stdin connected to a terminal.")); + + for (;;) { + const char *reply; + + puts(_("Commit Body is:")); + puts("--------------------------"); + printf("%s", state->msg); + puts("--------------------------"); + + /* + * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] + * in your translation. The program will only accept English + * input at this point. + */ + reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO); + + if (!reply) { + continue; + } else if (*reply == 'y' || *reply == 'Y') { + return 0; + } else if (*reply == 'a' || *reply == 'A') { + state->interactive = 0; + return 0; + } else if (*reply == 'n' || *reply == 'N') { + return 1; + } else if (*reply == 'e' || *reply == 'E') { + struct strbuf msg = STRBUF_INIT; + + if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) { + free(state->msg); + state->msg = strbuf_detach(&msg, &state->msg_len); + } + strbuf_release(&msg); + } else if (*reply == 'v' || *reply == 'V') { + const char *pager = git_pager(1); + struct child_process cp = CHILD_PROCESS_INIT; + + if (!pager) + pager = "cat"; + argv_array_push(&cp.args, pager); + argv_array_push(&cp.args, am_path(state, "patch")); + run_command(&cp); + } + } +} + /** * Applies all queued mail. * @@ -1702,6 +1793,9 @@ static void am_run(struct am_state *state, int resume) write_commit_msg(state); } + if (state->interactive && do_interactive(state)) + goto next; + if (run_applypatch_msg_hook(state)) exit(1); @@ -1787,10 +1881,17 @@ static void am_resolve(struct am_state *state) die_user_resolve(state); } + if (state->interactive) { + write_index_patch(state); + if (do_interactive(state)) + goto next; + } + rerere(0); do_commit(state); +next: am_next(state); am_run(state, 0); } @@ -2036,6 +2137,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) }; struct option options[] = { + OPT_BOOL('i', "interactive", &state.interactive, + N_("run interactively")), OPT_BOOL('3', "3way", &state.threeway, N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), -- cgit v1.2.3 From c2676cde9f2c2a67b3860f7fc69e4dc8597e505f Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:04 +0800 Subject: builtin-am: implement legacy -b/--binary option The -b/--binary option was initially implemented in 087b674 (git-am: --binary; document --resume and --binary., 2005-11-16). The option will pass the --binary flag to git-apply to allow it to apply binary patches. However, in 2b6eef9 (Make apply --binary a no-op., 2006-09-06), --binary was been made a no-op in git-apply. Following that, since cb3a160 (git-am: ignore --binary option, 2008-08-09), the --binary option in git-am is ignored as well. In 6c15a1c (am: officially deprecate -b/--binary option, 2012-03-13), the --binary option was tweaked to its present behavior: when set, the message: The -b/--binary option has been a no-op for long time, and it will be removed. Please do not use it anymore. will be printed. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 589199f417..3c50392013 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2126,6 +2126,7 @@ enum resume_mode { int cmd_am(int argc, const char **argv, const char *prefix) { struct am_state state; + int binary = -1; int keep_cr = -1; int patch_format = PATCH_FORMAT_UNKNOWN; enum resume_mode resume = RESUME_FALSE; @@ -2139,6 +2140,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOL('i', "interactive", &state.interactive, N_("run interactively")), + OPT_HIDDEN_BOOL('b', "binary", &binary, + N_("(historical option -- no-op")), OPT_BOOL('3', "3way", &state.threeway, N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), @@ -2239,6 +2242,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, usage, 0); + if (binary >= 0) + fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n" + "it will be removed. Please do not use it anymore.")); + if (read_index_preload(&the_index, NULL) < 0) die(_("failed to read the index")); -- cgit v1.2.3 From 5e4f9cff3cf2c6398f7abbbb0631dde8063a6fdc Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:05 +0800 Subject: builtin-am: check for valid committer ident When commit_tree() is called, if the user does not have an explicit committer ident configured, it will attempt to construct a default committer ident based on the user's and system's info (e.g. gecos field, hostname etc.) However, if a default committer ident is unable to be constructed, commit_tree() will die(), but at this point of git-am's execution, there will already be changes made to the index and work tree. This can be confusing to new users, and as such since d64e6b0 (Keep Porcelainish from failing by broken ident after making changes., 2006-02-18) git-am.sh will check to see if the committer ident has been configured, or a default one can be constructed, before even starting to apply patches. Re-implement this in builtin/am.c. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- builtin/am.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/am.c b/builtin/am.c index 3c50392013..1ff74ac136 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2246,6 +2246,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n" "it will be removed. Please do not use it anymore.")); + /* Ensure a valid committer ident can be constructed */ + git_committer_info(IDENT_STRICT); + if (read_index_preload(&the_index, NULL) < 0) die(_("failed to read the index")); -- cgit v1.2.3 From 783d7e865ec8f6190f8d3abe3ab72a9410d611f1 Mon Sep 17 00:00:00 2001 From: Paul Tan Date: Tue, 4 Aug 2015 21:52:06 +0800 Subject: builtin-am: remove redirection to git-am.sh At the beginning of the rewrite of git-am.sh to C, in order to not break existing test scripts that depended on a functional git-am, a redirection to git-am.sh was introduced that would activate if the environment variable _GIT_USE_BUILTIN_AM was not defined. Now that all of git-am.sh's functionality has been re-implemented in builtin/am.c, remove this redirection, and retire git-am.sh into contrib/examples/. Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- Makefile | 1 - builtin/am.c | 15 - contrib/examples/git-am.sh | 975 +++++++++++++++++++++++++++++++++++++++++++++ git-am.sh | 975 --------------------------------------------- git.c | 7 +- 5 files changed, 976 insertions(+), 997 deletions(-) create mode 100755 contrib/examples/git-am.sh delete mode 100755 git-am.sh diff --git a/Makefile b/Makefile index da451f8bca..e39ca6ca64 100644 --- a/Makefile +++ b/Makefile @@ -467,7 +467,6 @@ TEST_PROGRAMS_NEED_X = # interactive shell sessions without exporting it. unexport CDPATH -SCRIPT_SH += git-am.sh SCRIPT_SH += git-bisect.sh SCRIPT_SH += git-difftool--helper.sh SCRIPT_SH += git-filter-branch.sh diff --git a/builtin/am.c b/builtin/am.c index 1ff74ac136..84d57d4297 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2221,21 +2221,6 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_END() }; - /* - * NEEDSWORK: Once all the features of git-am.sh have been - * re-implemented in builtin/am.c, this preamble can be removed. - */ - if (!getenv("_GIT_USE_BUILTIN_AM")) { - const char *path = mkpath("%s/git-am", git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno("could not exec %s", path); - } else { - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); - } - git_config(git_default_config, NULL); am_state_init(&state, git_path("rebase-apply")); diff --git a/contrib/examples/git-am.sh b/contrib/examples/git-am.sh new file mode 100755 index 0000000000..3b77028123 --- /dev/null +++ b/contrib/examples/git-am.sh @@ -0,0 +1,975 @@ +#!/bin/sh +# +# Copyright (c) 2005, 2006 Junio C Hamano + +SUBDIRECTORY_OK=Yes +OPTIONS_KEEPDASHDASH= +OPTIONS_STUCKLONG=t +OPTIONS_SPEC="\ +git am [options] [(|)...] +git am [options] (--continue | --skip | --abort) +-- +i,interactive run interactively +b,binary* (historical option -- no-op) +3,3way allow fall back on 3way merging if needed +q,quiet be quiet +s,signoff add a Signed-off-by line to the commit message +u,utf8 recode into utf8 (default) +k,keep pass -k flag to git-mailinfo +keep-non-patch pass -b flag to git-mailinfo +m,message-id pass -m flag to git-mailinfo +keep-cr pass --keep-cr flag to git-mailsplit for mbox format +no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr +c,scissors strip everything before a scissors line +whitespace= pass it through git-apply +ignore-space-change pass it through git-apply +ignore-whitespace pass it through git-apply +directory= pass it through git-apply +exclude= pass it through git-apply +include= pass it through git-apply +C= pass it through git-apply +p= pass it through git-apply +patch-format= format the patch(es) are in +reject pass it through git-apply +resolvemsg= override error message when patch failure occurs +continue continue applying patches after resolving a conflict +r,resolved synonyms for --continue +skip skip the current patch +abort restore the original branch and abort the patching operation. +committer-date-is-author-date lie about committer date +ignore-date use current timestamp for author date +rerere-autoupdate update the index with reused conflict resolution if possible +S,gpg-sign? GPG-sign commits +rebasing* (internal use for git-rebase)" + +. git-sh-setup +. git-sh-i18n +prefix=$(git rev-parse --show-prefix) +set_reflog_action am +require_work_tree +cd_to_toplevel + +git var GIT_COMMITTER_IDENT >/dev/null || + die "$(gettext "You need to set your committer info first")" + +if git rev-parse --verify -q HEAD >/dev/null +then + HAS_HEAD=yes +else + HAS_HEAD= +fi + +cmdline="git am" +if test '' != "$interactive" +then + cmdline="$cmdline -i" +fi +if test '' != "$threeway" +then + cmdline="$cmdline -3" +fi + +empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 + +sq () { + git rev-parse --sq-quote "$@" +} + +stop_here () { + echo "$1" >"$dotest/next" + git rev-parse --verify -q HEAD >"$dotest/abort-safety" + exit 1 +} + +safe_to_abort () { + if test -f "$dotest/dirtyindex" + then + return 1 + fi + + if ! test -f "$dotest/abort-safety" + then + return 0 + fi + + abort_safety=$(cat "$dotest/abort-safety") + if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety" + then + return 0 + fi + gettextln "You seem to have moved HEAD since the last 'am' failure. +Not rewinding to ORIG_HEAD" >&2 + return 1 +} + +stop_here_user_resolve () { + if [ -n "$resolvemsg" ]; then + printf '%s\n' "$resolvemsg" + stop_here $1 + fi + eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\". +If you prefer to skip this patch, run \"\$cmdline --skip\" instead. +To restore the original branch and stop patching, run \"\$cmdline --abort\"." + + stop_here $1 +} + +go_next () { + rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \ + "$dotest/patch" "$dotest/info" + echo "$next" >"$dotest/next" + this=$next +} + +cannot_fallback () { + echo "$1" + gettextln "Cannot fall back to three-way merge." + exit 1 +} + +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. + cmd="git apply $git_apply_opt --build-fake-ancestor" && + cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' && + eval "$cmd" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git write-tree >"$dotest/patch-merge-base+" || + cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")" + + say "$(gettext "Using index info to reconstruct a base tree...")" + + cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"' + + if test -z "$GIT_QUIET" + then + eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD" + fi + + cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"' + if eval "$cmd" + then + mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" + mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" + else + cannot_fallback "$(gettext "Did you hand edit your patch? +It does not apply to blobs recorded in its 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 + + say "$(gettext "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 recursive ends up canceling them, + # saying that we reverted all those changes. + + eval GITHEAD_$his_tree='"$FIRSTLINE"' + export GITHEAD_$his_tree + if test -n "$GIT_QUIET" + then + GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY + fi + our_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) + git-merge-recursive $orig_tree -- $our_tree $his_tree || { + git rerere $allow_rerere_autoupdate + die "$(gettext "Failed to merge in the changes.")" + } + unset GITHEAD_$his_tree +} + +clean_abort () { + test $# = 0 || echo >&2 "$@" + rm -fr "$dotest" + exit 1 +} + +patch_format= + +check_patch_format () { + # early return if patch_format was set from the command line + if test -n "$patch_format" + then + return 0 + fi + + # we default to mbox format if input is from stdin and for + # directories + if test $# = 0 || test "x$1" = "x-" || test -d "$1" + then + patch_format=mbox + return 0 + fi + + # otherwise, check the first few non-blank lines of the first + # patch to try to detect its format + { + # Start from first line containing non-whitespace + l1= + while test -z "$l1" + do + read l1 || break + done + read l2 + read l3 + case "$l1" in + "From "* | "From: "*) + patch_format=mbox + ;; + '# This series applies on GIT commit'*) + patch_format=stgit-series + ;; + "# HG changeset patch") + patch_format=hg + ;; + *) + # if the second line is empty and the third is + # a From, Author or Date entry, this is very + # likely an StGIT patch + case "$l2,$l3" in + ,"From: "* | ,"Author: "* | ,"Date: "*) + patch_format=stgit + ;; + *) + ;; + esac + ;; + esac + if test -z "$patch_format" && + test -n "$l1" && + test -n "$l2" && + test -n "$l3" + then + # This begins with three non-empty lines. Is this a + # piece of e-mail a-la RFC2822? Grab all the headers, + # discarding the indented remainder of folded lines, + # and see if it looks like that they all begin with the + # header field names... + tr -d '\015' <"$1" | + sed -n -e '/^$/q' -e '/^[ ]/d' -e p | + sane_egrep -v '^[!-9;-~]+:' >/dev/null || + patch_format=mbox + fi + } < "$1" || clean_abort +} + +split_patches () { + case "$patch_format" in + mbox) + if test t = "$keepcr" + then + keep_cr=--keep-cr + else + keep_cr= + fi + git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" || + clean_abort + ;; + stgit-series) + if test $# -ne 1 + then + clean_abort "$(gettext "Only one StGIT patch series can be applied at once")" + fi + series_dir=$(dirname "$1") + series_file="$1" + shift + { + set x + while read filename + do + set "$@" "$series_dir/$filename" + done + # remove the safety x + shift + # remove the arg coming from the first-line comment + shift + } < "$series_file" || clean_abort + # set the patch format appropriately + patch_format=stgit + # now handle the actual StGIT patches + split_patches "$@" + ;; + stgit) + this=0 + test 0 -eq "$#" && set -- - + for stgit in "$@" + do + this=$(expr "$this" + 1) + msgnum=$(printf "%0${prec}d" $this) + # Perl version of StGIT parse_patch. The first nonemptyline + # not starting with Author, From or Date is the + # subject, and the body starts with the next nonempty + # line not starting with Author, From or Date + @@PERL@@ -ne 'BEGIN { $subject = 0 } + if ($subject > 1) { print ; } + elsif (/^\s+$/) { next ; } + elsif (/^Author:/) { s/Author/From/ ; print ;} + elsif (/^(From|Date)/) { print ; } + elsif ($subject) { + $subject = 2 ; + print "\n" ; + print ; + } else { + print "Subject: ", $_ ; + $subject = 1; + } + ' -- "$stgit" >"$dotest/$msgnum" || clean_abort + done + echo "$this" > "$dotest/last" + this= + msgnum= + ;; + hg) + this=0 + test 0 -eq "$#" && set -- - + for hg in "$@" + do + this=$(( $this + 1 )) + msgnum=$(printf "%0${prec}d" $this) + # hg stores changeset metadata in #-commented lines preceding + # the commit message and diff(s). The only metadata we care about + # are the User and Date (Node ID and Parent are hashes which are + # only relevant to the hg repository and thus not useful to us) + # Since we cannot guarantee that the commit message is in + # git-friendly format, we put no Subject: line and just consume + # all of the message as the body + LANG=C LC_ALL=C @@PERL@@ -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 } + if ($subject) { print ; } + elsif (/^\# User /) { s/\# User/From:/ ; print ; } + elsif (/^\# Date /) { + my ($hashsign, $str, $time, $tz) = split ; + $tz_str = sprintf "%+05d", (0-$tz)/36; + print "Date: " . + strftime("%a, %d %b %Y %H:%M:%S ", + gmtime($time-$tz)) + . "$tz_str\n"; + } elsif (/^\# /) { next ; } + else { + print "\n", $_ ; + $subject = 1; + } + ' -- "$hg" >"$dotest/$msgnum" || clean_abort + done + echo "$this" >"$dotest/last" + this= + msgnum= + ;; + *) + if test -n "$patch_format" + then + clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")" + else + clean_abort "$(gettext "Patch format detection failed.")" + fi + ;; + esac +} + +prec=4 +dotest="$GIT_DIR/rebase-apply" +sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort= +messageid= resolvemsg= resume= scissors= no_inbody_headers= +git_apply_opt= +committer_date_is_author_date= +ignore_date= +allow_rerere_autoupdate= +gpg_sign_opt= +threeway= + +if test "$(git config --bool --get am.messageid)" = true +then + messageid=t +fi + +if test "$(git config --bool --get am.keepcr)" = true +then + keepcr=t +fi + +while test $# != 0 +do + case "$1" in + -i|--interactive) + interactive=t ;; + -b|--binary) + gettextln >&2 "The -b/--binary option has been a no-op for long time, and +it will be removed. Please do not use it anymore." + ;; + -3|--3way) + threeway=t ;; + -s|--signoff) + sign=t ;; + -u|--utf8) + utf8=t ;; # this is now default + --no-utf8) + utf8= ;; + -m|--message-id) + messageid=t ;; + --no-message-id) + messageid=f ;; + -k|--keep) + keep=t ;; + --keep-non-patch) + keep=b ;; + -c|--scissors) + scissors=t ;; + --no-scissors) + scissors=f ;; + -r|--resolved|--continue) + resolved=t ;; + --skip) + skip=t ;; + --abort) + abort=t ;; + --rebasing) + rebasing=t threeway=t ;; + --resolvemsg=*) + resolvemsg="${1#--resolvemsg=}" ;; + --whitespace=*|--directory=*|--exclude=*|--include=*) + git_apply_opt="$git_apply_opt $(sq "$1")" ;; + -C*|-p*) + git_apply_opt="$git_apply_opt $(sq "$1")" ;; + --patch-format=*) + patch_format="${1#--patch-format=}" ;; + --reject|--ignore-whitespace|--ignore-space-change) + git_apply_opt="$git_apply_opt $1" ;; + --committer-date-is-author-date) + committer_date_is_author_date=t ;; + --ignore-date) + ignore_date=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" ;; + -q|--quiet) + GIT_QUIET=t ;; + --keep-cr) + keepcr=t ;; + --no-keep-cr) + keepcr=f ;; + --gpg-sign) + gpg_sign_opt=-S ;; + --gpg-sign=*) + gpg_sign_opt="-S${1#--gpg-sign=}" ;; + --) + shift; break ;; + *) + usage ;; + esac + shift +done + +# If the dotest directory exists, but we have finished applying all the +# patches in them, clear it out. +if test -d "$dotest" && + test -f "$dotest/last" && + test -f "$dotest/next" && + last=$(cat "$dotest/last") && + next=$(cat "$dotest/next") && + test $# != 0 && + test "$next" -gt "$last" +then + rm -fr "$dotest" +fi + +if test -d "$dotest" && test -f "$dotest/last" && test -f "$dotest/next" +then + case "$#,$skip$resolved$abort" in + 0,*t*) + # Explicit resume command and we do not have file, so + # we are happy. + : ;; + 0,) + # No file input but without resume parameters; catch + # user error to feed us a patch from standard input + # when there is already $dotest. This is somewhat + # unreliable -- stdin could be /dev/null for example + # and the caller did not intend to feed us a patch but + # wanted to continue unattended. + test -t 0 + ;; + *) + false + ;; + esac || + die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")" + resume=yes + + case "$skip,$abort" in + t,t) + die "$(gettext "Please make up your mind. --skip or --abort?")" + ;; + t,) + git rerere clear + head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) && + git read-tree --reset -u $head_tree $head_tree && + index_tree=$(git write-tree) && + git read-tree -m -u $index_tree $head_tree + git read-tree $head_tree + ;; + ,t) + if test -f "$dotest/rebasing" + then + exec git rebase --abort + fi + git rerere clear + if safe_to_abort + then + head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) && + git read-tree --reset -u $head_tree $head_tree && + index_tree=$(git write-tree) && + orig_head=$(git rev-parse --verify -q ORIG_HEAD || echo $empty_tree) && + git read-tree -m -u $index_tree $orig_head + if git rev-parse --verify -q ORIG_HEAD >/dev/null 2>&1 + then + git reset ORIG_HEAD + else + git read-tree $empty_tree + curr_branch=$(git symbolic-ref HEAD 2>/dev/null) && + git update-ref -d $curr_branch + fi + fi + rm -fr "$dotest" + exit ;; + esac + rm -f "$dotest/dirtyindex" +else + # Possible stray $dotest directory in the independent-run + # case; in the --rebasing case, it is upto the caller + # (git-rebase--am) to take care of stray directories. + if test -d "$dotest" && test -z "$rebasing" + then + case "$skip,$resolved,$abort" in + ,,t) + rm -fr "$dotest" + exit 0 + ;; + *) + die "$(eval_gettext "Stray \$dotest directory found. +Use \"git am --abort\" to remove it.")" + ;; + esac + fi + + # Make sure we are not given --skip, --continue, or --abort + test "$skip$resolved$abort" = "" || + die "$(gettext "Resolve operation not in progress, we are not resuming.")" + + # Start afresh. + mkdir -p "$dotest" || exit + + if test -n "$prefix" && test $# != 0 + then + first=t + for arg + do + test -n "$first" && { + set x + first= + } + if is_absolute_path "$arg" + then + set "$@" "$arg" + else + set "$@" "$prefix$arg" + fi + done + shift + fi + + check_patch_format "$@" + + split_patches "$@" + + # -i can and must be given when resuming; everything + # else is kept + echo " $git_apply_opt" >"$dotest/apply-opt" + echo "$threeway" >"$dotest/threeway" + echo "$sign" >"$dotest/sign" + echo "$utf8" >"$dotest/utf8" + echo "$keep" >"$dotest/keep" + echo "$messageid" >"$dotest/messageid" + echo "$scissors" >"$dotest/scissors" + echo "$no_inbody_headers" >"$dotest/no_inbody_headers" + echo "$GIT_QUIET" >"$dotest/quiet" + echo 1 >"$dotest/next" + if test -n "$rebasing" + then + : >"$dotest/rebasing" + else + : >"$dotest/applying" + if test -n "$HAS_HEAD" + then + git update-ref ORIG_HEAD HEAD + else + git update-ref -d ORIG_HEAD >/dev/null 2>&1 + fi + fi +fi + +git update-index -q --refresh + +case "$resolved" in +'') + case "$HAS_HEAD" in + '') + files=$(git ls-files) ;; + ?*) + files=$(git diff-index --cached --name-only HEAD --) ;; + esac || exit + if test "$files" + then + test -n "$HAS_HEAD" && : >"$dotest/dirtyindex" + die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")" + fi +esac + +# Now, decide what command line options we will give to the git +# commands we invoke, based on the result of parsing command line +# options and previous invocation state stored in $dotest/ files. + +if test "$(cat "$dotest/utf8")" = t +then + utf8=-u +else + utf8=-n +fi +keep=$(cat "$dotest/keep") +case "$keep" in +t) + keep=-k ;; +b) + keep=-b ;; +*) + keep= ;; +esac +case "$(cat "$dotest/messageid")" in +t) + messageid=-m ;; +f) + messageid= ;; +esac +case "$(cat "$dotest/scissors")" in +t) + scissors=--scissors ;; +f) + scissors=--no-scissors ;; +esac +if test "$(cat "$dotest/no_inbody_headers")" = t +then + no_inbody_headers=--no-inbody-headers +else + no_inbody_headers= +fi +if test "$(cat "$dotest/quiet")" = t +then + GIT_QUIET=t +fi +if test "$(cat "$dotest/threeway")" = t +then + threeway=t +fi +git_apply_opt=$(cat "$dotest/apply-opt") +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 + +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 --continue flag. + case "$resume" in + '') + if test -f "$dotest/rebasing" + then + commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \ + -e q "$dotest/$msgnum") && + test "$(git cat-file -t "$commit")" = commit || + stop_here $this + git cat-file commit "$commit" | + sed -e '1,/^$/d' >"$dotest/msg-clean" + echo "$commit" >"$dotest/original-commit" + get_author_ident_from_commit "$commit" >"$dotest/author-script" + git diff-tree --root --binary --full-index "$commit" >"$dotest/patch" + else + git mailinfo $keep $no_inbody_headers $messageid $scissors $utf8 "$dotest/msg" "$dotest/patch" \ + <"$dotest/$msgnum" >"$dotest/info" || + stop_here $this + + # skip pine's internal folder data + sane_grep '^Author: Mail System Internal Data$' \ + <"$dotest"/info >/dev/null && + go_next && continue + + test -s "$dotest/patch" || { + eval_gettextln "Patch is empty. Was it split wrong? +If you would prefer to skip this patch, instead run \"\$cmdline --skip\". +To restore the original branch and stop patching run \"\$cmdline --abort\"." + stop_here $this + } + rm -f "$dotest/original-commit" "$dotest/author-script" + { + sed -n '/^Subject/ s/Subject: //p' "$dotest/info" + echo + cat "$dotest/msg" + } | + git stripspace > "$dotest/msg-clean" + fi + ;; + esac + + if test -f "$dotest/author-script" + then + eval $(cat "$dotest/author-script") + else + 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")" + fi + + if test -z "$GIT_AUTHOR_EMAIL" + then + gettextln "Patch does not have a valid e-mail address." + stop_here $this + fi + + export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + + case "$resume" in + '') + if test '' != "$SIGNOFF" + then + LAST_SIGNED_OFF_BY=$( + sed -ne '/^Signed-off-by: /p' \ + "$dotest/msg-clean" | + sed -ne '$p' + ) + ADD_SIGNOFF=$( + test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { + test '' = "$LAST_SIGNED_OFF_BY" && echo + echo "$SIGNOFF" + }) + else + ADD_SIGNOFF= + fi + { + if test -s "$dotest/msg-clean" + then + 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 "$(gettext "cannot be interactive without stdin connected to a terminal.")" + action=again + while test "$action" = again + do + gettextln "Commit Body is:" + echo "--------------------------" + cat "$dotest/final-commit" + echo "--------------------------" + # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] + # in your translation. The program will only accept English + # input at this point. + gettext "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]*) git_editor "$dotest/final-commit" + action=again ;; + [vV]*) action=again + git_pager "$dotest/patch" ;; + *) action=again ;; + esac + done + else + action=yes + fi + + if test $action = skip + then + go_next + continue + fi + + hook="$(git rev-parse --git-path hooks/applypatch-msg)" + if test -x "$hook" + then + "$hook" "$dotest/final-commit" || stop_here $this + fi + + if test -f "$dotest/final-commit" + then + FIRSTLINE=$(sed 1q "$dotest/final-commit") + else + FIRSTLINE="" + fi + + say "$(eval_gettext "Applying: \$FIRSTLINE")" + + case "$resolved" in + '') + # When we are allowed to fall back to 3-way later, don't give + # false errors during the initial attempt. + squelch= + if test "$threeway" = t + then + squelch='>/dev/null 2>&1 ' + fi + eval "git apply $squelch$git_apply_opt"' --index "$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= + git diff-index --quiet --cached HEAD -- && { + gettextln "No changes - did you forget to use 'git add'? +If there is nothing left to stage, chances are that something else +already introduced the same changes; you might want to skip this patch." + stop_here_user_resolve $this + } + unmerged=$(git ls-files -u) + if test -n "$unmerged" + then + gettextln "You still have unmerged paths in your index +did you forget to use 'git add'?" + stop_here_user_resolve $this + fi + apply_status=0 + git rerere + ;; + esac + + if test $apply_status != 0 && 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. + git diff-index --quiet --cached HEAD -- && { + say "$(gettext "No changes -- Patch already applied.")" + go_next + continue + } + # clear apply_status -- we have successfully merged. + apply_status=0 + fi + fi + if test $apply_status != 0 + then + eval_gettextln 'Patch failed at $msgnum $FIRSTLINE' + if test "$(git config --bool advice.amworkdir)" != false + then + eval_gettextln 'The copy of the patch that failed is found in: + $dotest/patch' + fi + stop_here_user_resolve $this + fi + + hook="$(git rev-parse --git-path hooks/pre-applypatch)" + if test -x "$hook" + then + "$hook" || stop_here $this + fi + + tree=$(git write-tree) && + commit=$( + if test -n "$ignore_date" + then + GIT_AUTHOR_DATE= + fi + parent=$(git rev-parse --verify -q HEAD) || + say >&2 "$(gettext "applying to an empty history")" + + if test -n "$committer_date_is_author_date" + then + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + export GIT_COMMITTER_DATE + fi && + git commit-tree ${parent:+-p} $parent ${gpg_sign_opt:+"$gpg_sign_opt"} $tree \ + <"$dotest/final-commit" + ) && + git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || + stop_here $this + + if test -f "$dotest/original-commit"; then + echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten" + fi + + hook="$(git rev-parse --git-path hooks/post-applypatch)" + test -x "$hook" && "$hook" + + go_next +done + +if test -s "$dotest"/rewritten; then + git notes copy --for-rewrite=rebase < "$dotest"/rewritten + hook="$(git rev-parse --git-path hooks/post-rewrite)" + if test -x "$hook"; then + "$hook" rebase < "$dotest"/rewritten + fi +fi + +# If am was called with --rebasing (from git-rebase--am), it's up to +# the caller to take care of housekeeping. +if ! test -f "$dotest/rebasing" +then + rm -fr "$dotest" + git gc --auto +fi diff --git a/git-am.sh b/git-am.sh deleted file mode 100755 index 3b77028123..0000000000 --- a/git-am.sh +++ /dev/null @@ -1,975 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005, 2006 Junio C Hamano - -SUBDIRECTORY_OK=Yes -OPTIONS_KEEPDASHDASH= -OPTIONS_STUCKLONG=t -OPTIONS_SPEC="\ -git am [options] [(|)...] -git am [options] (--continue | --skip | --abort) --- -i,interactive run interactively -b,binary* (historical option -- no-op) -3,3way allow fall back on 3way merging if needed -q,quiet be quiet -s,signoff add a Signed-off-by line to the commit message -u,utf8 recode into utf8 (default) -k,keep pass -k flag to git-mailinfo -keep-non-patch pass -b flag to git-mailinfo -m,message-id pass -m flag to git-mailinfo -keep-cr pass --keep-cr flag to git-mailsplit for mbox format -no-keep-cr do not pass --keep-cr flag to git-mailsplit independent of am.keepcr -c,scissors strip everything before a scissors line -whitespace= pass it through git-apply -ignore-space-change pass it through git-apply -ignore-whitespace pass it through git-apply -directory= pass it through git-apply -exclude= pass it through git-apply -include= pass it through git-apply -C= pass it through git-apply -p= pass it through git-apply -patch-format= format the patch(es) are in -reject pass it through git-apply -resolvemsg= override error message when patch failure occurs -continue continue applying patches after resolving a conflict -r,resolved synonyms for --continue -skip skip the current patch -abort restore the original branch and abort the patching operation. -committer-date-is-author-date lie about committer date -ignore-date use current timestamp for author date -rerere-autoupdate update the index with reused conflict resolution if possible -S,gpg-sign? GPG-sign commits -rebasing* (internal use for git-rebase)" - -. git-sh-setup -. git-sh-i18n -prefix=$(git rev-parse --show-prefix) -set_reflog_action am -require_work_tree -cd_to_toplevel - -git var GIT_COMMITTER_IDENT >/dev/null || - die "$(gettext "You need to set your committer info first")" - -if git rev-parse --verify -q HEAD >/dev/null -then - HAS_HEAD=yes -else - HAS_HEAD= -fi - -cmdline="git am" -if test '' != "$interactive" -then - cmdline="$cmdline -i" -fi -if test '' != "$threeway" -then - cmdline="$cmdline -3" -fi - -empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - -sq () { - git rev-parse --sq-quote "$@" -} - -stop_here () { - echo "$1" >"$dotest/next" - git rev-parse --verify -q HEAD >"$dotest/abort-safety" - exit 1 -} - -safe_to_abort () { - if test -f "$dotest/dirtyindex" - then - return 1 - fi - - if ! test -f "$dotest/abort-safety" - then - return 0 - fi - - abort_safety=$(cat "$dotest/abort-safety") - if test "z$(git rev-parse --verify -q HEAD)" = "z$abort_safety" - then - return 0 - fi - gettextln "You seem to have moved HEAD since the last 'am' failure. -Not rewinding to ORIG_HEAD" >&2 - return 1 -} - -stop_here_user_resolve () { - if [ -n "$resolvemsg" ]; then - printf '%s\n' "$resolvemsg" - stop_here $1 - fi - eval_gettextln "When you have resolved this problem, run \"\$cmdline --continue\". -If you prefer to skip this patch, run \"\$cmdline --skip\" instead. -To restore the original branch and stop patching, run \"\$cmdline --abort\"." - - stop_here $1 -} - -go_next () { - rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \ - "$dotest/patch" "$dotest/info" - echo "$next" >"$dotest/next" - this=$next -} - -cannot_fallback () { - echo "$1" - gettextln "Cannot fall back to three-way merge." - exit 1 -} - -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. - cmd="git apply $git_apply_opt --build-fake-ancestor" && - cmd="$cmd "'"$dotest/patch-merge-tmp-index" "$dotest/patch"' && - eval "$cmd" && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git write-tree >"$dotest/patch-merge-base+" || - cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")" - - say "$(gettext "Using index info to reconstruct a base tree...")" - - cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"' - - if test -z "$GIT_QUIET" - then - eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD" - fi - - cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"' - if eval "$cmd" - then - mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" - mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" - else - cannot_fallback "$(gettext "Did you hand edit your patch? -It does not apply to blobs recorded in its 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 - - say "$(gettext "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 recursive ends up canceling them, - # saying that we reverted all those changes. - - eval GITHEAD_$his_tree='"$FIRSTLINE"' - export GITHEAD_$his_tree - if test -n "$GIT_QUIET" - then - GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY - fi - our_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) - git-merge-recursive $orig_tree -- $our_tree $his_tree || { - git rerere $allow_rerere_autoupdate - die "$(gettext "Failed to merge in the changes.")" - } - unset GITHEAD_$his_tree -} - -clean_abort () { - test $# = 0 || echo >&2 "$@" - rm -fr "$dotest" - exit 1 -} - -patch_format= - -check_patch_format () { - # early return if patch_format was set from the command line - if test -n "$patch_format" - then - return 0 - fi - - # we default to mbox format if input is from stdin and for - # directories - if test $# = 0 || test "x$1" = "x-" || test -d "$1" - then - patch_format=mbox - return 0 - fi - - # otherwise, check the first few non-blank lines of the first - # patch to try to detect its format - { - # Start from first line containing non-whitespace - l1= - while test -z "$l1" - do - read l1 || break - done - read l2 - read l3 - case "$l1" in - "From "* | "From: "*) - patch_format=mbox - ;; - '# This series applies on GIT commit'*) - patch_format=stgit-series - ;; - "# HG changeset patch") - patch_format=hg - ;; - *) - # if the second line is empty and the third is - # a From, Author or Date entry, this is very - # likely an StGIT patch - case "$l2,$l3" in - ,"From: "* | ,"Author: "* | ,"Date: "*) - patch_format=stgit - ;; - *) - ;; - esac - ;; - esac - if test -z "$patch_format" && - test -n "$l1" && - test -n "$l2" && - test -n "$l3" - then - # This begins with three non-empty lines. Is this a - # piece of e-mail a-la RFC2822? Grab all the headers, - # discarding the indented remainder of folded lines, - # and see if it looks like that they all begin with the - # header field names... - tr -d '\015' <"$1" | - sed -n -e '/^$/q' -e '/^[ ]/d' -e p | - sane_egrep -v '^[!-9;-~]+:' >/dev/null || - patch_format=mbox - fi - } < "$1" || clean_abort -} - -split_patches () { - case "$patch_format" in - mbox) - if test t = "$keepcr" - then - keep_cr=--keep-cr - else - keep_cr= - fi - git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" || - clean_abort - ;; - stgit-series) - if test $# -ne 1 - then - clean_abort "$(gettext "Only one StGIT patch series can be applied at once")" - fi - series_dir=$(dirname "$1") - series_file="$1" - shift - { - set x - while read filename - do - set "$@" "$series_dir/$filename" - done - # remove the safety x - shift - # remove the arg coming from the first-line comment - shift - } < "$series_file" || clean_abort - # set the patch format appropriately - patch_format=stgit - # now handle the actual StGIT patches - split_patches "$@" - ;; - stgit) - this=0 - test 0 -eq "$#" && set -- - - for stgit in "$@" - do - this=$(expr "$this" + 1) - msgnum=$(printf "%0${prec}d" $this) - # Perl version of StGIT parse_patch. The first nonemptyline - # not starting with Author, From or Date is the - # subject, and the body starts with the next nonempty - # line not starting with Author, From or Date - @@PERL@@ -ne 'BEGIN { $subject = 0 } - if ($subject > 1) { print ; } - elsif (/^\s+$/) { next ; } - elsif (/^Author:/) { s/Author/From/ ; print ;} - elsif (/^(From|Date)/) { print ; } - elsif ($subject) { - $subject = 2 ; - print "\n" ; - print ; - } else { - print "Subject: ", $_ ; - $subject = 1; - } - ' -- "$stgit" >"$dotest/$msgnum" || clean_abort - done - echo "$this" > "$dotest/last" - this= - msgnum= - ;; - hg) - this=0 - test 0 -eq "$#" && set -- - - for hg in "$@" - do - this=$(( $this + 1 )) - msgnum=$(printf "%0${prec}d" $this) - # hg stores changeset metadata in #-commented lines preceding - # the commit message and diff(s). The only metadata we care about - # are the User and Date (Node ID and Parent are hashes which are - # only relevant to the hg repository and thus not useful to us) - # Since we cannot guarantee that the commit message is in - # git-friendly format, we put no Subject: line and just consume - # all of the message as the body - LANG=C LC_ALL=C @@PERL@@ -M'POSIX qw(strftime)' -ne 'BEGIN { $subject = 0 } - if ($subject) { print ; } - elsif (/^\# User /) { s/\# User/From:/ ; print ; } - elsif (/^\# Date /) { - my ($hashsign, $str, $time, $tz) = split ; - $tz_str = sprintf "%+05d", (0-$tz)/36; - print "Date: " . - strftime("%a, %d %b %Y %H:%M:%S ", - gmtime($time-$tz)) - . "$tz_str\n"; - } elsif (/^\# /) { next ; } - else { - print "\n", $_ ; - $subject = 1; - } - ' -- "$hg" >"$dotest/$msgnum" || clean_abort - done - echo "$this" >"$dotest/last" - this= - msgnum= - ;; - *) - if test -n "$patch_format" - then - clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")" - else - clean_abort "$(gettext "Patch format detection failed.")" - fi - ;; - esac -} - -prec=4 -dotest="$GIT_DIR/rebase-apply" -sign= utf8=t keep= keepcr= skip= interactive= resolved= rebasing= abort= -messageid= resolvemsg= resume= scissors= no_inbody_headers= -git_apply_opt= -committer_date_is_author_date= -ignore_date= -allow_rerere_autoupdate= -gpg_sign_opt= -threeway= - -if test "$(git config --bool --get am.messageid)" = true -then - messageid=t -fi - -if test "$(git config --bool --get am.keepcr)" = true -then - keepcr=t -fi - -while test $# != 0 -do - case "$1" in - -i|--interactive) - interactive=t ;; - -b|--binary) - gettextln >&2 "The -b/--binary option has been a no-op for long time, and -it will be removed. Please do not use it anymore." - ;; - -3|--3way) - threeway=t ;; - -s|--signoff) - sign=t ;; - -u|--utf8) - utf8=t ;; # this is now default - --no-utf8) - utf8= ;; - -m|--message-id) - messageid=t ;; - --no-message-id) - messageid=f ;; - -k|--keep) - keep=t ;; - --keep-non-patch) - keep=b ;; - -c|--scissors) - scissors=t ;; - --no-scissors) - scissors=f ;; - -r|--resolved|--continue) - resolved=t ;; - --skip) - skip=t ;; - --abort) - abort=t ;; - --rebasing) - rebasing=t threeway=t ;; - --resolvemsg=*) - resolvemsg="${1#--resolvemsg=}" ;; - --whitespace=*|--directory=*|--exclude=*|--include=*) - git_apply_opt="$git_apply_opt $(sq "$1")" ;; - -C*|-p*) - git_apply_opt="$git_apply_opt $(sq "$1")" ;; - --patch-format=*) - patch_format="${1#--patch-format=}" ;; - --reject|--ignore-whitespace|--ignore-space-change) - git_apply_opt="$git_apply_opt $1" ;; - --committer-date-is-author-date) - committer_date_is_author_date=t ;; - --ignore-date) - ignore_date=t ;; - --rerere-autoupdate|--no-rerere-autoupdate) - allow_rerere_autoupdate="$1" ;; - -q|--quiet) - GIT_QUIET=t ;; - --keep-cr) - keepcr=t ;; - --no-keep-cr) - keepcr=f ;; - --gpg-sign) - gpg_sign_opt=-S ;; - --gpg-sign=*) - gpg_sign_opt="-S${1#--gpg-sign=}" ;; - --) - shift; break ;; - *) - usage ;; - esac - shift -done - -# If the dotest directory exists, but we have finished applying all the -# patches in them, clear it out. -if test -d "$dotest" && - test -f "$dotest/last" && - test -f "$dotest/next" && - last=$(cat "$dotest/last") && - next=$(cat "$dotest/next") && - test $# != 0 && - test "$next" -gt "$last" -then - rm -fr "$dotest" -fi - -if test -d "$dotest" && test -f "$dotest/last" && test -f "$dotest/next" -then - case "$#,$skip$resolved$abort" in - 0,*t*) - # Explicit resume command and we do not have file, so - # we are happy. - : ;; - 0,) - # No file input but without resume parameters; catch - # user error to feed us a patch from standard input - # when there is already $dotest. This is somewhat - # unreliable -- stdin could be /dev/null for example - # and the caller did not intend to feed us a patch but - # wanted to continue unattended. - test -t 0 - ;; - *) - false - ;; - esac || - die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")" - resume=yes - - case "$skip,$abort" in - t,t) - die "$(gettext "Please make up your mind. --skip or --abort?")" - ;; - t,) - git rerere clear - head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) && - git read-tree --reset -u $head_tree $head_tree && - index_tree=$(git write-tree) && - git read-tree -m -u $index_tree $head_tree - git read-tree $head_tree - ;; - ,t) - if test -f "$dotest/rebasing" - then - exec git rebase --abort - fi - git rerere clear - if safe_to_abort - then - head_tree=$(git rev-parse --verify -q HEAD || echo $empty_tree) && - git read-tree --reset -u $head_tree $head_tree && - index_tree=$(git write-tree) && - orig_head=$(git rev-parse --verify -q ORIG_HEAD || echo $empty_tree) && - git read-tree -m -u $index_tree $orig_head - if git rev-parse --verify -q ORIG_HEAD >/dev/null 2>&1 - then - git reset ORIG_HEAD - else - git read-tree $empty_tree - curr_branch=$(git symbolic-ref HEAD 2>/dev/null) && - git update-ref -d $curr_branch - fi - fi - rm -fr "$dotest" - exit ;; - esac - rm -f "$dotest/dirtyindex" -else - # Possible stray $dotest directory in the independent-run - # case; in the --rebasing case, it is upto the caller - # (git-rebase--am) to take care of stray directories. - if test -d "$dotest" && test -z "$rebasing" - then - case "$skip,$resolved,$abort" in - ,,t) - rm -fr "$dotest" - exit 0 - ;; - *) - die "$(eval_gettext "Stray \$dotest directory found. -Use \"git am --abort\" to remove it.")" - ;; - esac - fi - - # Make sure we are not given --skip, --continue, or --abort - test "$skip$resolved$abort" = "" || - die "$(gettext "Resolve operation not in progress, we are not resuming.")" - - # Start afresh. - mkdir -p "$dotest" || exit - - if test -n "$prefix" && test $# != 0 - then - first=t - for arg - do - test -n "$first" && { - set x - first= - } - if is_absolute_path "$arg" - then - set "$@" "$arg" - else - set "$@" "$prefix$arg" - fi - done - shift - fi - - check_patch_format "$@" - - split_patches "$@" - - # -i can and must be given when resuming; everything - # else is kept - echo " $git_apply_opt" >"$dotest/apply-opt" - echo "$threeway" >"$dotest/threeway" - echo "$sign" >"$dotest/sign" - echo "$utf8" >"$dotest/utf8" - echo "$keep" >"$dotest/keep" - echo "$messageid" >"$dotest/messageid" - echo "$scissors" >"$dotest/scissors" - echo "$no_inbody_headers" >"$dotest/no_inbody_headers" - echo "$GIT_QUIET" >"$dotest/quiet" - echo 1 >"$dotest/next" - if test -n "$rebasing" - then - : >"$dotest/rebasing" - else - : >"$dotest/applying" - if test -n "$HAS_HEAD" - then - git update-ref ORIG_HEAD HEAD - else - git update-ref -d ORIG_HEAD >/dev/null 2>&1 - fi - fi -fi - -git update-index -q --refresh - -case "$resolved" in -'') - case "$HAS_HEAD" in - '') - files=$(git ls-files) ;; - ?*) - files=$(git diff-index --cached --name-only HEAD --) ;; - esac || exit - if test "$files" - then - test -n "$HAS_HEAD" && : >"$dotest/dirtyindex" - die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")" - fi -esac - -# Now, decide what command line options we will give to the git -# commands we invoke, based on the result of parsing command line -# options and previous invocation state stored in $dotest/ files. - -if test "$(cat "$dotest/utf8")" = t -then - utf8=-u -else - utf8=-n -fi -keep=$(cat "$dotest/keep") -case "$keep" in -t) - keep=-k ;; -b) - keep=-b ;; -*) - keep= ;; -esac -case "$(cat "$dotest/messageid")" in -t) - messageid=-m ;; -f) - messageid= ;; -esac -case "$(cat "$dotest/scissors")" in -t) - scissors=--scissors ;; -f) - scissors=--no-scissors ;; -esac -if test "$(cat "$dotest/no_inbody_headers")" = t -then - no_inbody_headers=--no-inbody-headers -else - no_inbody_headers= -fi -if test "$(cat "$dotest/quiet")" = t -then - GIT_QUIET=t -fi -if test "$(cat "$dotest/threeway")" = t -then - threeway=t -fi -git_apply_opt=$(cat "$dotest/apply-opt") -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 - -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 --continue flag. - case "$resume" in - '') - if test -f "$dotest/rebasing" - then - commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \ - -e q "$dotest/$msgnum") && - test "$(git cat-file -t "$commit")" = commit || - stop_here $this - git cat-file commit "$commit" | - sed -e '1,/^$/d' >"$dotest/msg-clean" - echo "$commit" >"$dotest/original-commit" - get_author_ident_from_commit "$commit" >"$dotest/author-script" - git diff-tree --root --binary --full-index "$commit" >"$dotest/patch" - else - git mailinfo $keep $no_inbody_headers $messageid $scissors $utf8 "$dotest/msg" "$dotest/patch" \ - <"$dotest/$msgnum" >"$dotest/info" || - stop_here $this - - # skip pine's internal folder data - sane_grep '^Author: Mail System Internal Data$' \ - <"$dotest"/info >/dev/null && - go_next && continue - - test -s "$dotest/patch" || { - eval_gettextln "Patch is empty. Was it split wrong? -If you would prefer to skip this patch, instead run \"\$cmdline --skip\". -To restore the original branch and stop patching run \"\$cmdline --abort\"." - stop_here $this - } - rm -f "$dotest/original-commit" "$dotest/author-script" - { - sed -n '/^Subject/ s/Subject: //p' "$dotest/info" - echo - cat "$dotest/msg" - } | - git stripspace > "$dotest/msg-clean" - fi - ;; - esac - - if test -f "$dotest/author-script" - then - eval $(cat "$dotest/author-script") - else - 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")" - fi - - if test -z "$GIT_AUTHOR_EMAIL" - then - gettextln "Patch does not have a valid e-mail address." - stop_here $this - fi - - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE - - case "$resume" in - '') - if test '' != "$SIGNOFF" - then - LAST_SIGNED_OFF_BY=$( - sed -ne '/^Signed-off-by: /p' \ - "$dotest/msg-clean" | - sed -ne '$p' - ) - ADD_SIGNOFF=$( - test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { - test '' = "$LAST_SIGNED_OFF_BY" && echo - echo "$SIGNOFF" - }) - else - ADD_SIGNOFF= - fi - { - if test -s "$dotest/msg-clean" - then - 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 "$(gettext "cannot be interactive without stdin connected to a terminal.")" - action=again - while test "$action" = again - do - gettextln "Commit Body is:" - echo "--------------------------" - cat "$dotest/final-commit" - echo "--------------------------" - # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] - # in your translation. The program will only accept English - # input at this point. - gettext "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]*) git_editor "$dotest/final-commit" - action=again ;; - [vV]*) action=again - git_pager "$dotest/patch" ;; - *) action=again ;; - esac - done - else - action=yes - fi - - if test $action = skip - then - go_next - continue - fi - - hook="$(git rev-parse --git-path hooks/applypatch-msg)" - if test -x "$hook" - then - "$hook" "$dotest/final-commit" || stop_here $this - fi - - if test -f "$dotest/final-commit" - then - FIRSTLINE=$(sed 1q "$dotest/final-commit") - else - FIRSTLINE="" - fi - - say "$(eval_gettext "Applying: \$FIRSTLINE")" - - case "$resolved" in - '') - # When we are allowed to fall back to 3-way later, don't give - # false errors during the initial attempt. - squelch= - if test "$threeway" = t - then - squelch='>/dev/null 2>&1 ' - fi - eval "git apply $squelch$git_apply_opt"' --index "$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= - git diff-index --quiet --cached HEAD -- && { - gettextln "No changes - did you forget to use 'git add'? -If there is nothing left to stage, chances are that something else -already introduced the same changes; you might want to skip this patch." - stop_here_user_resolve $this - } - unmerged=$(git ls-files -u) - if test -n "$unmerged" - then - gettextln "You still have unmerged paths in your index -did you forget to use 'git add'?" - stop_here_user_resolve $this - fi - apply_status=0 - git rerere - ;; - esac - - if test $apply_status != 0 && 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. - git diff-index --quiet --cached HEAD -- && { - say "$(gettext "No changes -- Patch already applied.")" - go_next - continue - } - # clear apply_status -- we have successfully merged. - apply_status=0 - fi - fi - if test $apply_status != 0 - then - eval_gettextln 'Patch failed at $msgnum $FIRSTLINE' - if test "$(git config --bool advice.amworkdir)" != false - then - eval_gettextln 'The copy of the patch that failed is found in: - $dotest/patch' - fi - stop_here_user_resolve $this - fi - - hook="$(git rev-parse --git-path hooks/pre-applypatch)" - if test -x "$hook" - then - "$hook" || stop_here $this - fi - - tree=$(git write-tree) && - commit=$( - if test -n "$ignore_date" - then - GIT_AUTHOR_DATE= - fi - parent=$(git rev-parse --verify -q HEAD) || - say >&2 "$(gettext "applying to an empty history")" - - if test -n "$committer_date_is_author_date" - then - GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" - export GIT_COMMITTER_DATE - fi && - git commit-tree ${parent:+-p} $parent ${gpg_sign_opt:+"$gpg_sign_opt"} $tree \ - <"$dotest/final-commit" - ) && - git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || - stop_here $this - - if test -f "$dotest/original-commit"; then - echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten" - fi - - hook="$(git rev-parse --git-path hooks/post-applypatch)" - test -x "$hook" && "$hook" - - go_next -done - -if test -s "$dotest"/rewritten; then - git notes copy --for-rewrite=rebase < "$dotest"/rewritten - hook="$(git rev-parse --git-path hooks/post-rewrite)" - if test -x "$hook"; then - "$hook" rebase < "$dotest"/rewritten - fi -fi - -# If am was called with --rebasing (from git-rebase--am), it's up to -# the caller to take care of housekeeping. -if ! test -f "$dotest/rebasing" -then - rm -fr "$dotest" - git gc --auto -fi diff --git a/git.c b/git.c index 38d9ad531e..5feba410ca 100644 --- a/git.c +++ b/git.c @@ -370,12 +370,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) static struct cmd_struct commands[] = { { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - /* - * NEEDSWORK: Once the redirection to git-am.sh in builtin/am.c has - * been removed, this entry should be changed to - * RUN_SETUP | NEED_WORK_TREE - */ - { "am", cmd_am }, + { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE }, { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, { "archive", cmd_archive }, -- cgit v1.2.3 From e97a5e765db06995c3b027a5d7dceb6b4f2cba02 Mon Sep 17 00:00:00 2001 From: Remi Lespinet Date: Tue, 4 Aug 2015 22:19:26 +0800 Subject: git-am: add am.threeWay config variable Add the am.threeWay configuration variable to use the -3 or --3way option of git am by default. When am.threeway is set and not desired for a specific git am command, the --no-3way option can be used to override it. Signed-off-by: Remi Lespinet Signed-off-by: Paul Tan Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++++++++ Documentation/git-am.txt | 7 +++++-- builtin/am.c | 2 ++ t/t4150-am.sh | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 315f2710af..fb3fc57911 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -769,6 +769,14 @@ am.keepcr:: by giving '--no-keep-cr' from the command line. See linkgit:git-am[1], linkgit:git-mailsplit[1]. +am.threeWay:: + By default, `git am` will fail if the patch does not apply cleanly. When + set to true, this setting tells `git am` to fall back on 3-way merge if + the patch records the identity of blobs it is supposed to apply to and + we have those blobs available locally (equivalent to giving the `--3way` + option from the command line). Defaults to `false`. + See linkgit:git-am[1]. + apply.ignoreWhitespace:: When set to 'change', tells 'git apply' to ignore changes in whitespace, in the same way as the '--ignore-space-change' diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 0d8ba48f79..dbea6e7ae9 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] - [--3way] [--interactive] [--committer-date-is-author-date] + [--[no-]3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=