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 <pyoka...@gmail.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to