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 <pyokagan@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul Tan committed Aug 4, 2015 at 21:51 UTC df2760a576a8d26e26a6a11aa497e347c7402fd3
1 file changed +133 -1
builtin/am.c
+133 -1
@@ -21,6 +21,8 @@
21 #include "sequencer.h"
22 #include "revision.h"
23 #include "merge-recursive.h"
24 +#include "revision.h"
25 +#include "log-tree.h"
26
27 /**
28 * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -787,6 +789,129 @@ finish:
789 return ret;
790 }
791
792 +/**
793 + * Sets commit_id to the commit hash where the mail was generated from.
794 + * Returns 0 on success, -1 on failure.
795 + */
796 +static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)
797 +{
798 + struct strbuf sb = STRBUF_INIT;
799 + FILE *fp = xfopen(mail, "r");
800 + const char *x;
801 +
802 + if (strbuf_getline(&sb, fp, '\n'))
803 + return -1;
804 +
805 + if (!skip_prefix(sb.buf, "From ", &x))
806 + return -1;
807 +
808 + if (get_sha1_hex(x, commit_id) < 0)
809 + return -1;
810 +
811 + strbuf_release(&sb);
812 + fclose(fp);
813 + return 0;
814 +}
815 +
816 +/**
817 + * Sets state->msg, state->author_name, state->author_email, state->author_date
818 + * to the commit's respective info.
819 + */
820 +static void get_commit_info(struct am_state *state, struct commit *commit)
821 +{
822 + const char *buffer, *ident_line, *author_date, *msg;
823 + size_t ident_len;
824 + struct ident_split ident_split;
825 + struct strbuf sb = STRBUF_INIT;
826 +
827 + buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
828 +
829 + ident_line = find_commit_header(buffer, "author", &ident_len);
830 +
831 + if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
832 + strbuf_add(&sb, ident_line, ident_len);
833 + die(_("invalid ident line: %s"), sb.buf);
834 + }
835 +
836 + assert(!state->author_name);
837 + if (ident_split.name_begin) {
838 + strbuf_add(&sb, ident_split.name_begin,
839 + ident_split.name_end - ident_split.name_begin);
840 + state->author_name = strbuf_detach(&sb, NULL);
841 + } else
842 + state->author_name = xstrdup("");
843 +
844 + assert(!state->author_email);
845 + if (ident_split.mail_begin) {
846 + strbuf_add(&sb, ident_split.mail_begin,
847 + ident_split.mail_end - ident_split.mail_begin);
848 + state->author_email = strbuf_detach(&sb, NULL);
849 + } else
850 + state->author_email = xstrdup("");
851 +
852 + author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));
853 + strbuf_addstr(&sb, author_date);
854 + assert(!state->author_date);
855 + state->author_date = strbuf_detach(&sb, NULL);
856 +
857 + assert(!state->msg);
858 + msg = strstr(buffer, "\n\n");
859 + if (!msg)
860 + die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1));
861 + state->msg = xstrdup(msg + 2);
862 + state->msg_len = strlen(state->msg);
863 +}
864 +
865 +/**
866 + * Writes `commit` as a patch to the state directory's "patch" file.
867 + */
868 +static void write_commit_patch(const struct am_state *state, struct commit *commit)
869 +{
870 + struct rev_info rev_info;
871 + FILE *fp;
872 +
873 + fp = xfopen(am_path(state, "patch"), "w");
874 + init_revisions(&rev_info, NULL);
875 + rev_info.diff = 1;
876 + rev_info.abbrev = 0;
877 + rev_info.disable_stdin = 1;
878 + rev_info.show_root_diff = 1;
879 + rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
880 + rev_info.no_commit_id = 1;
881 + DIFF_OPT_SET(&rev_info.diffopt, BINARY);
882 + DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
883 + rev_info.diffopt.use_color = 0;
884 + rev_info.diffopt.file = fp;
885 + rev_info.diffopt.close_file = 1;
886 + add_pending_object(&rev_info, &commit->object, "");
887 + diff_setup_done(&rev_info.diffopt);
888 + log_tree_commit(&rev_info, commit);
889 +}
890 +
891 +/**
892 + * Like parse_mail(), but parses the mail by looking up its commit ID
893 + * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
894 + * of patches.
895 + *
896 + * Will always return 0 as the patch should never be skipped.
897 + */
898 +static int parse_mail_rebase(struct am_state *state, const char *mail)
899 +{
900 + struct commit *commit;
901 + unsigned char commit_sha1[GIT_SHA1_RAWSZ];
902 +
903 + if (get_mail_commit_sha1(commit_sha1, mail) < 0)
904 + die(_("could not parse %s"), mail);
905 +
906 + commit = lookup_commit_or_die(commit_sha1, mail);
907 +
908 + get_commit_info(state, commit);
909 +
910 + write_commit_patch(state, commit);
911 +
912 + return 0;
913 +}
914 +
915 /**
916 * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
917 * `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)
1144 validate_resume_state(state);
1145 resume = 0;
1146 } else {
1022 - if (parse_mail(state, mail))
1147 + int skip;
1148 +
1149 + if (state->rebasing)
1150 + skip = parse_mail_rebase(state, mail);
1151 + else
1152 + skip = parse_mail(state, mail);
1153 +
1154 + if (skip)
1155 goto next; /* mail should be skipped */
1156
1157 write_author_script(state);