Merge branch 'jk/format-patch-from'
authorJunio C Hamano <gitster@pobox.com>
Mon, 15 Jul 2013 17:28:39 +0000 (10:28 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 15 Jul 2013 17:28:40 +0000 (10:28 -0700)
"git format-patch" learned "--from[=whom]" option, which sets the
"From: " header to the specified person (or the person who runs the
command, if "=whom" part is missing) and move the original author
information to an in-body From: header as necessary.

* jk/format-patch-from:
teach format-patch to place other authors into in-body "From"
pretty.c: drop const-ness from pretty_print_context

Documentation/git-format-patch.txt
builtin/log.c
commit.h
log-tree.c
pretty.c
revision.h
t/t4014-format-patch.sh
index 39118774afbbd730e4464f44e24a05af91432c30..e394276b1a2e2edfbbf8ab1dea9fcd30ca66e61f 100644 (file)
@@ -187,6 +187,21 @@ will want to ensure that threading is disabled for `git send-email`.
        The negated form `--no-cc` discards all `Cc:` headers added so
        far (from config or command line).
 
+--from::
+--from=<ident>::
+       Use `ident` in the `From:` header of each commit email. If the
+       author ident of the commit is not textually identical to the
+       provided `ident`, place a `From:` header in the body of the
+       message with the original author. If no `ident` is given, use
+       the committer ident.
++
+Note that this option is only useful if you are actually sending the
+emails and want to identify yourself as the sender, but retain the
+original author (and `git am` will correctly pick up the in-body
+header). Note also that `git send-email` already handles this
+transformation for you, and this option should not be used if you are
+feeding the result to `git send-email`.
+
 --add-header=<header>::
        Add an arbitrary header to the email headers.  This is in addition
        to any configured headers, and may be used multiple times.
index e3222ed9f979e3140cbfc88ad2839c573c89bf06..2625f9881ad836d1a3e303f4c159d6b76a3ae04f 100644 (file)
@@ -1112,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+static int from_callback(const struct option *opt, const char *arg, int unset)
+{
+       char **from = opt->value;
+
+       free(*from);
+
+       if (unset)
+               *from = NULL;
+       else if (arg)
+               *from = xstrdup(arg);
+       else
+               *from = xstrdup(git_committer_info(IDENT_NO_DATE));
+       return 0;
+}
+
 int cmd_format_patch(int argc, const char **argv, const char *prefix)
 {
        struct commit *commit;
@@ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int quiet = 0;
        int reroll_count = -1;
        char *branch_name = NULL;
+       char *from = NULL;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            N_("use [PATCH n/m] even with a single patch"),
@@ -1177,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                            0, to_callback },
                { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
                            0, cc_callback },
+               { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+                           N_("set From address to <ident> (or committer ident if absent)"),
+                           PARSE_OPT_OPTARG, from_callback },
                OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
                            N_("make first mail a reply to <message-id>")),
                { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@@ -1264,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        rev.extra_headers = strbuf_detach(&buf, NULL);
 
+       if (from) {
+               if (split_ident_line(&rev.from_ident, from, strlen(from)))
+                       die(_("invalid ident line: %s"), from);
+       }
+
        if (start_number < 0)
                start_number = 1;
 
index 18a523495eb7be3636f4f1311375fdb35329da9e..35cc4e266bd4afe0e00d2efb2781db2e95906936 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -6,6 +6,7 @@
 #include "strbuf.h"
 #include "decorate.h"
 #include "gpg-interface.h"
+#include "string-list.h"
 
 struct commit_list {
        struct commit *item;
@@ -79,6 +80,9 @@ enum cmit_fmt {
 };
 
 struct pretty_print_context {
+       /*
+        * Callers should tweak these to change the behavior of pp_* functions.
+        */
        enum cmit_fmt fmt;
        int abbrev;
        const char *subject;
@@ -92,6 +96,15 @@ struct pretty_print_context {
        const char *output_encoding;
        struct string_list *mailmap;
        int color;
+       struct ident_split *from_ident;
+
+       /*
+        * Fields below here are manipulated internally by pp_* functions and
+        * should not be counted on by callers.
+        */
+
+       /* Manipulated by the pp_* functions internally. */
+       struct string_list in_body_headers;
 };
 
 struct userformat_want {
@@ -111,20 +124,20 @@ extern void userformat_find_requirements(const char *fmt, struct userformat_want
 extern void format_commit_message(const struct commit *commit,
                                  const char *format, struct strbuf *sb,
                                  const struct pretty_print_context *context);
-extern void pretty_print_commit(const struct pretty_print_context *pp,
+extern void pretty_print_commit(struct pretty_print_context *pp,
                                const struct commit *commit,
                                struct strbuf *sb);
 extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
                           struct strbuf *sb);
-void pp_user_info(const struct pretty_print_context *pp,
+void pp_user_info(struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding);
-void pp_title_line(const struct pretty_print_context *pp,
+void pp_title_line(struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
                   const char *encoding,
                   int need_8bit_cte);
-void pp_remainder(const struct pretty_print_context *pp,
+void pp_remainder(struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent);
index 60f32a3965feeba30bd7ba7ba724c5d7dfd52be8..a49d8e895d3ad24f00e8504ec3eccfbddead6b4c 100644 (file)
@@ -618,6 +618,8 @@ void show_log(struct rev_info *opt)
        ctx.mailmap = opt->mailmap;
        ctx.color = opt->diffopt.use_color;
        ctx.output_encoding = get_log_output_encoding();
+       if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
+               ctx.from_ident = &opt->from_ident;
        pretty_print_commit(&ctx, commit, &msgbuf);
 
        if (opt->add_signoff)
index 9e431545d8e6c14401faea5e76296974ca1d6649..74563c92b4cdce8e947a2816a8b56355b11439e6 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -406,7 +406,7 @@ static const char *show_ident_date(const struct ident_split *ident,
        return show_date(date, tz, mode);
 }
 
-void pp_user_info(const struct pretty_print_context *pp,
+void pp_user_info(struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding)
 {
@@ -432,6 +432,23 @@ void pp_user_info(const struct pretty_print_context *pp,
                map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
 
        if (pp->fmt == CMIT_FMT_EMAIL) {
+               if (pp->from_ident) {
+                       struct strbuf buf = STRBUF_INIT;
+
+                       strbuf_addstr(&buf, "From: ");
+                       strbuf_add(&buf, namebuf, namelen);
+                       strbuf_addstr(&buf, " <");
+                       strbuf_add(&buf, mailbuf, maillen);
+                       strbuf_addstr(&buf, ">\n");
+                       string_list_append(&pp->in_body_headers,
+                                          strbuf_detach(&buf, NULL));
+
+                       mailbuf = pp->from_ident->mail_begin;
+                       maillen = pp->from_ident->mail_end - mailbuf;
+                       namebuf = pp->from_ident->name_begin;
+                       namelen = pp->from_ident->name_end - namebuf;
+               }
+
                strbuf_addstr(sb, "From: ");
                if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
                        add_rfc2047(sb, namebuf, namelen,
@@ -1514,7 +1531,7 @@ void format_commit_message(const struct commit *commit,
        free(context.signature_check.signer);
 }
 
-static void pp_header(const struct pretty_print_context *pp,
+static void pp_header(struct pretty_print_context *pp,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
@@ -1575,7 +1592,7 @@ static void pp_header(const struct pretty_print_context *pp,
        }
 }
 
-void pp_title_line(const struct pretty_print_context *pp,
+void pp_title_line(struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
                   const char *encoding,
@@ -1602,6 +1619,16 @@ void pp_title_line(const struct pretty_print_context *pp,
        }
        strbuf_addch(sb, '\n');
 
+       if (need_8bit_cte == 0) {
+               int i;
+               for (i = 0; i < pp->in_body_headers.nr; i++) {
+                       if (has_non_ascii(pp->in_body_headers.items[i].string)) {
+                               need_8bit_cte = 1;
+                               break;
+                       }
+               }
+       }
+
        if (need_8bit_cte > 0) {
                const char *header_fmt =
                        "MIME-Version: 1.0\n"
@@ -1615,10 +1642,21 @@ void pp_title_line(const struct pretty_print_context *pp,
        if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addch(sb, '\n');
        }
+
+       if (pp->in_body_headers.nr) {
+               int i;
+               for (i = 0; i < pp->in_body_headers.nr; i++) {
+                       strbuf_addstr(sb, pp->in_body_headers.items[i].string);
+                       free(pp->in_body_headers.items[i].string);
+               }
+               string_list_clear(&pp->in_body_headers, 0);
+               strbuf_addch(sb, '\n');
+       }
+
        strbuf_release(&title);
 }
 
-void pp_remainder(const struct pretty_print_context *pp,
+void pp_remainder(struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent)
@@ -1650,7 +1688,7 @@ void pp_remainder(const struct pretty_print_context *pp,
        }
 }
 
-void pretty_print_commit(const struct pretty_print_context *pp,
+void pretty_print_commit(struct pretty_print_context *pp,
                         const struct commit *commit,
                         struct strbuf *sb)
 {
index 92d6614af6da62160219b0df40d0caf56335ffce..95859ba119033ba17346c701493df1474eec5df2 100644 (file)
@@ -144,6 +144,7 @@ struct rev_info {
        int             numbered_files;
        int             reroll_count;
        char            *message_id;
+       struct ident_split from_ident;
        struct string_list *ref_message_ids;
        int             add_signoff;
        const char      *extra_headers;
index 58d418098d793b284bb4a7222cf9709c45b26c90..668933bfb2c906ce9f8c74ab498f1986b56aced0 100755 (executable)
@@ -972,6 +972,49 @@ test_expect_success 'empty subject prefix does not have extra space' '
        test_cmp expect actual
 '
 
+test_expect_success '--from=ident notices bogus ident' '
+       test_must_fail git format-patch -1 --stdout --from=foo >patch
+'
+
+test_expect_success '--from=ident replaces author' '
+       git format-patch -1 --stdout --from="Me <me@example.com>" >patch &&
+       cat >expect <<-\EOF &&
+       From: Me <me@example.com>
+
+       From: A U Thor <author@example.com>
+
+       EOF
+       sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+       test_cmp expect patch.head
+'
+
+test_expect_success '--from uses committer ident' '
+       git format-patch -1 --stdout --from >patch &&
+       cat >expect <<-\EOF &&
+       From: C O Mitter <committer@example.com>
+
+       From: A U Thor <author@example.com>
+
+       EOF
+       sed -ne "/^From:/p; /^$/p; /^---$/q" <patch >patch.head &&
+       test_cmp expect patch.head
+'
+
+test_expect_success 'in-body headers trigger content encoding' '
+       GIT_AUTHOR_NAME="éxötìc" test_commit exotic &&
+       test_when_finished "git reset --hard HEAD^" &&
+       git format-patch -1 --stdout --from >patch &&
+       cat >expect <<-\EOF &&
+       From: C O Mitter <committer@example.com>
+       Content-Type: text/plain; charset=UTF-8
+
+       From: éxötìc <author@example.com>
+
+       EOF
+       sed -ne "/^From:/p; /^$/p; /^Content-Type/p; /^---$/q" <patch >patch.head &&
+       test_cmp expect patch.head
+'
+
 append_signoff()
 {
        C=$(git commit-tree HEAD^^{tree} -p HEAD) &&