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_patch() code path in git-am.sh's --rebasing mode that bypasses git-mailinfo. Re-implement this code path in builtin/am.c as parse_patch_rebase(). Signed-off-by: Paul Tan <pyokagan@xxxxxxxxx> --- builtin/am.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index 9afa3bb..0d7e37c 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. @@ -50,6 +52,30 @@ static const char *firstline(const char *msg) return sb.buf; } +/** + * Iterate over lines, each terminated by c, in a string, without modifying the + * string itself. begin will be set to the beginning of the line, while end + * will be set to the line termination or null character at the end of the line + * or string. On the first call, begin and end must be initialized to the + * string to be iterated over. E.g.: + * + * const char *begin, *end, *str = "1\n2\n3"; + * begin = end = str; + * while (!getbufline(&begin, &end, '\n')) + * ... + */ +static int getbufline(const char **begin, const char **end, int c) +{ + if (!**end) + return EOF; + else if (**end == c) + *begin = *end + 1; + else + *begin = *end; + *end = strchrnul(*begin, c); + return 0; +} + enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX @@ -675,6 +701,126 @@ static int parse_patch(struct am_state *state, const char *patch) } /** + * Sets commit_id to the commit hash where the patch was generated from. + * Returns 0 on success, -1 on failure. + */ +static int get_patch_commit_sha1(unsigned char *commit_id, const char *patch) +{ + struct strbuf sb = STRBUF_INIT; + FILE *fp = xfopen(patch, "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 *buf, *begin, *end; + + buf = logmsg_reencode(commit, NULL, get_commit_output_encoding()); + begin = end = buf; + + while (!getbufline(&begin, &end, '\n')) { + const char *x; + + if (skip_prefix(begin, "author ", &x)) { + struct ident_split split; + const char *date; + + if (split_ident_line(&split, x, end - x) < 0) { + struct strbuf sb = STRBUF_INIT; + + strbuf_add(&sb, x, end - x); + die(_("invalid ident line: %s"), sb.buf); + } + + if (split.name_begin) + strbuf_add(&state->author_name, + split.name_begin, + split.name_end - split.name_begin); + + if (split.mail_begin) + strbuf_add(&state->author_email, + split.mail_begin, + split.mail_end - split.mail_begin); + + date = show_ident_date(&split, DATE_NORMAL); + strbuf_addstr(&state->author_date, date); + } else if (begin == end) { + end++; + break; + } + } + + strbuf_addstr(&state->msg, end); +} + +/** + * Writes commit as a patch to $state_dir/patch. + */ +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_patch(), but parses the patch 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_patch_rebase(struct am_state *state, const char *patch) +{ + struct commit *commit; + unsigned char commit_sha1[GIT_SHA1_RAWSZ]; + + if (get_patch_commit_sha1(commit_sha1, patch) < 0) + die(_("could not parse %s"), patch); + + commit = lookup_commit_or_die(commit_sha1, patch); + + get_commit_info(state, commit); + + write_commit_patch(state, commit); + + return 0; +} + +/** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ @@ -885,12 +1031,17 @@ static void am_run(struct am_state *state) while (state->cur <= state->last) { const char *patch = am_path(state, msgnum(state)); - int apply_status; + int apply_status, skip; if (!file_exists(patch)) goto next; - if (parse_patch(state, patch)) + if (state->rebasing) + skip = parse_patch_rebase(state, patch); + else + skip = parse_patch(state, patch); + + if (skip) goto next; /* patch should be skipped */ write_author_script(state); -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html