format-patch: teach --cover-from-description option

Before, when format-patch generated a cover letter, only the body would be populated with a branch's description while the subject would be populated with placeholder text. However, users may want to have the subject of their cover letter automatically populated in the same way. Teach format-patch to accept the `--cover-from-description` option and corresponding `format.coverFromDescription` config, allowing users to populate different parts of the cover letter (including the subject now). Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Denton Liu committed Oct 15, 2019 at 02:06 UTC bf8e65b30b77308710f14d523782f99a4a23eb55
5 files changed +279 -21
Documentation/config/format.txt
+6
@@ -36,6 +36,12 @@ format.subjectPrefix::
36 The default for format-patch is to output files with the '[PATCH]'
37 subject prefix. Use this variable to change that prefix.
38
39 +format.coverFromDescription::
40 + The default mode for format-patch to determine which parts of
41 + the cover letter will be populated using the branch's
42 + description. See the `--cover-from-description` option in
43 + linkgit:git-format-patch[1].
44 +
45 format.signature::
46 The default for format-patch is to output a signature containing
47 the Git version number. Use this variable to change that default.
Documentation/git-format-patch.txt
+22
@@ -19,6 +19,7 @@ SYNOPSIS
19 [--start-number <n>] [--numbered-files]
20 [--in-reply-to=<message id>] [--suffix=.<sfx>]
21 [--ignore-if-in-upstream]
22 + [--cover-from-description=<mode>]
23 [--rfc] [--subject-prefix=<subject prefix>]
24 [(--reroll-count|-v) <n>]
25 [--to=<email>] [--cc=<email>]
@@ -171,6 +172,26 @@ will want to ensure that threading is disabled for `git send-email`.
172 patches being generated, and any patch that matches is
173 ignored.
174
175 +--cover-from-description=<mode>::
176 + Controls which parts of the cover letter will be automatically
177 + populated using the branch's description.
178 ++
179 +If `<mode>` is `message` or `default`, the cover letter subject will be
180 +populated with placeholder text. The body of the cover letter will be
181 +populated with the branch's description. This is the default mode when
182 +no configuration nor command line option is specified.
183 ++
184 +If `<mode>` is `subject`, the first paragraph of the branch description will
185 +populate the cover letter subject. The remainder of the description will
186 +populate the body of the cover letter.
187 ++
188 +If `<mode>` is `auto`, if the first paragraph of the branch description
189 +is greater than 100 bytes, then the mode will be `message`, otherwise
190 +`subject` will be used.
191 ++
192 +If `<mode>` is `none`, both the cover letter subject and body will be
193 +populated with placeholder text.
194 +
195 --subject-prefix=<subject prefix>::
196 Instead of the standard '[PATCH]' prefix in the subject
197 line, instead use '[<subject prefix>]'. This
@@ -347,6 +368,7 @@ with configuration variables.
368 signOff = true
369 outputDirectory = <directory>
370 coverLetter = auto
371 + coverFromDescription = auto
372 ------------
373
374
builtin/log.c
+75 -20
@@ -37,6 +37,7 @@
37 #include "range-diff.h"
38
39 #define MAIL_DEFAULT_WRAP 72
40 +#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
41
42 /* Set a default date-time format for git log ("log.date" config variable) */
43 static const char *default_date_mode = NULL;
@@ -777,6 +778,13 @@ enum thread_level {
778 THREAD_DEEP
779 };
780
781 +enum cover_from_description {
782 + COVER_FROM_NONE,
783 + COVER_FROM_MESSAGE,
784 + COVER_FROM_SUBJECT,
785 + COVER_FROM_AUTO
786 +};
787 +
788 static enum thread_level thread;
789 static int do_signoff;
790 static int base_auto;
@@ -785,6 +793,23 @@ static const char *signature = git_version_string;
793 static const char *signature_file;
794 static enum cover_setting config_cover_letter;
795 static const char *config_output_directory;
796 +static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE;
797 +
798 +static enum cover_from_description parse_cover_from_description(const char *arg)
799 +{
800 + if (!arg || !strcmp(arg, "default"))
801 + return COVER_FROM_MESSAGE;
802 + else if (!strcmp(arg, "none"))
803 + return COVER_FROM_NONE;
804 + else if (!strcmp(arg, "message"))
805 + return COVER_FROM_MESSAGE;
806 + else if (!strcmp(arg, "subject"))
807 + return COVER_FROM_SUBJECT;
808 + else if (!strcmp(arg, "auto"))
809 + return COVER_FROM_AUTO;
810 + else
811 + die(_("%s: invalid cover from description mode"), arg);
812 +}
813
814 static int git_format_config(const char *var, const char *value, void *cb)
815 {
@@ -891,6 +916,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
916 }
917 return 0;
918 }
919 + if (!strcmp(var, "format.coverfromdescription")) {
920 + cover_from_description_mode = parse_cover_from_description(value);
921 + return 0;
922 + }
923
924 return git_log_config(var, value, cb);
925 }
@@ -997,20 +1026,6 @@ static void print_signature(FILE *file)
1026 putc('\n', file);
1027 }
1028
1000 -static void add_branch_description(struct strbuf *buf, const char *branch_name)
1001 -{
1002 - struct strbuf desc = STRBUF_INIT;
1003 - if (!branch_name || !*branch_name)
1004 - return;
1005 - read_branch_desc(&desc, branch_name);
1006 - if (desc.len) {
1007 - strbuf_addch(buf, '\n');
1008 - strbuf_addbuf(buf, &desc);
1009 - strbuf_addch(buf, '\n');
1010 - }
1011 - strbuf_release(&desc);
1012 -}
1013 -
1029 static char *find_branch_name(struct rev_info *rev)
1030 {
1031 int i, positive = -1;
@@ -1057,6 +1072,44 @@ static void show_diffstat(struct rev_info *rev,
1072 fprintf(rev->diffopt.file, "\n");
1073 }
1074
1075 +static void prepare_cover_text(struct pretty_print_context *pp,
1076 + const char *branch_name,
1077 + struct strbuf *sb,
1078 + const char *encoding,
1079 + int need_8bit_cte)
1080 +{
1081 + const char *subject = "*** SUBJECT HERE ***";
1082 + const char *body = "*** BLURB HERE ***";
1083 + struct strbuf description_sb = STRBUF_INIT;
1084 + struct strbuf subject_sb = STRBUF_INIT;
1085 +
1086 + if (cover_from_description_mode == COVER_FROM_NONE)
1087 + goto do_pp;
1088 +
1089 + if (branch_name && *branch_name)
1090 + read_branch_desc(&description_sb, branch_name);
1091 + if (!description_sb.len)
1092 + goto do_pp;
1093 +
1094 + if (cover_from_description_mode == COVER_FROM_SUBJECT ||
1095 + cover_from_description_mode == COVER_FROM_AUTO)
1096 + body = format_subject(&subject_sb, description_sb.buf, " ");
1097 +
1098 + if (cover_from_description_mode == COVER_FROM_MESSAGE ||
1099 + (cover_from_description_mode == COVER_FROM_AUTO &&
1100 + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN))
1101 + body = description_sb.buf;
1102 + else
1103 + subject = subject_sb.buf;
1104 +
1105 +do_pp:
1106 + pp_title_line(pp, &subject, sb, encoding, need_8bit_cte);
1107 + pp_remainder(pp, &body, sb, 0);
1108 +
1109 + strbuf_release(&description_sb);
1110 + strbuf_release(&subject_sb);
1111 +}
1112 +
1113 static void make_cover_letter(struct rev_info *rev, int use_stdout,
1114 struct commit *origin,
1115 int nr, struct commit **list,
@@ -1064,8 +1117,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
1117 int quiet)
1118 {
1119 const char *committer;
1067 - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
1068 - const char *msg;
1120 struct shortlog log;
1121 struct strbuf sb = STRBUF_INIT;
1122 int i;
@@ -1095,15 +1146,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
1146 if (!branch_name)
1147 branch_name = find_branch_name(rev);
1148
1098 - msg = body;
1149 pp.fmt = CMIT_FMT_EMAIL;
1150 pp.date_mode.type = DATE_RFC2822;
1151 pp.rev = rev;
1152 pp.print_email_subject = 1;
1153 pp_user_info(&pp, NULL, &sb, committer, encoding);
1104 - pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
1105 - pp_remainder(&pp, &msg, &sb, 0);
1106 - add_branch_description(&sb, branch_name);
1154 + prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
1155 fprintf(rev->diffopt.file, "%s\n", sb.buf);
1156
1157 strbuf_release(&sb);
@@ -1545,6 +1593,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1593 int use_patch_format = 0;
1594 int quiet = 0;
1595 int reroll_count = -1;
1596 + char *cover_from_description_arg = NULL;
1597 char *branch_name = NULL;
1598 char *base_commit = NULL;
1599 struct base_tree_info bases;
@@ -1581,6 +1630,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1630 { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
1631 N_("Use [RFC PATCH] instead of [PATCH]"),
1632 PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
1633 + OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
1634 + N_("cover-from-description-mode"),
1635 + N_("generate parts of a cover letter based on a branch's description")),
1636 { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
1637 N_("Use [<prefix>] instead of [PATCH]"),
1638 PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1672,6 +1724,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1724 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
1725 PARSE_OPT_KEEP_DASHDASH);
1726
1727 + if (cover_from_description_arg)
1728 + cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
1729 +
1730 if (0 < reroll_count) {
1731 struct strbuf sprefix = STRBUF_INIT;
1732 strbuf_addf(&sprefix, "%s v%d",
t/t4014-format-patch.sh
+172
@@ -1517,6 +1517,178 @@ test_expect_success 'format patch ignores color.ui' '
1517 test_cmp expect actual
1518 '
1519
1520 +test_expect_success 'cover letter with invalid --cover-from-description and config' '
1521 + test_config branch.rebuild-1.description "config subject
1522 +
1523 +body" &&
1524 + test_must_fail git format-patch --cover-letter --cover-from-description garbage master &&
1525 + test_config format.coverFromDescription garbage &&
1526 + test_must_fail git format-patch --cover-letter master
1527 +'
1528 +
1529 +test_expect_success 'cover letter with format.coverFromDescription = default' '
1530 + test_config branch.rebuild-1.description "config subject
1531 +
1532 +body" &&
1533 + test_config format.coverFromDescription default &&
1534 + git checkout rebuild-1 &&
1535 + git format-patch --stdout --cover-letter master >actual &&
1536 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1537 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1538 + grep "^config subject$" actual &&
1539 + grep "^body$" actual
1540 +'
1541 +
1542 +test_expect_success 'cover letter with --cover-from-description default' '
1543 + test_config branch.rebuild-1.description "config subject
1544 +
1545 +body" &&
1546 + git checkout rebuild-1 &&
1547 + git format-patch --stdout --cover-letter --cover-from-description default master >actual &&
1548 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1549 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1550 + grep "^config subject$" actual &&
1551 + grep "^body$" actual
1552 +'
1553 +
1554 +test_expect_success 'cover letter with format.coverFromDescription = none' '
1555 + test_config branch.rebuild-1.description "config subject
1556 +
1557 +body" &&
1558 + test_config format.coverFromDescription none &&
1559 + git checkout rebuild-1 &&
1560 + git format-patch --stdout --cover-letter master >actual &&
1561 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1562 + grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1563 + ! grep "^config subject$" actual &&
1564 + ! grep "^body$" actual
1565 +'
1566 +
1567 +test_expect_success 'cover letter with --cover-from-description none' '
1568 + test_config branch.rebuild-1.description "config subject
1569 +
1570 +body" &&
1571 + git checkout rebuild-1 &&
1572 + git format-patch --stdout --cover-letter --cover-from-description none master >actual &&
1573 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1574 + grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1575 + ! grep "^config subject$" actual &&
1576 + ! grep "^body$" actual
1577 +'
1578 +
1579 +test_expect_success 'cover letter with format.coverFromDescription = message' '
1580 + test_config branch.rebuild-1.description "config subject
1581 +
1582 +body" &&
1583 + test_config format.coverFromDescription message &&
1584 + git checkout rebuild-1 &&
1585 + git format-patch --stdout --cover-letter master >actual &&
1586 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1587 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1588 + grep "^config subject$" actual &&
1589 + grep "^body$" actual
1590 +'
1591 +
1592 +test_expect_success 'cover letter with --cover-from-description message' '
1593 + test_config branch.rebuild-1.description "config subject
1594 +
1595 +body" &&
1596 + git checkout rebuild-1 &&
1597 + git format-patch --stdout --cover-letter --cover-from-description message master >actual &&
1598 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1599 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1600 + grep "^config subject$" actual &&
1601 + grep "^body$" actual
1602 +'
1603 +
1604 +test_expect_success 'cover letter with format.coverFromDescription = subject' '
1605 + test_config branch.rebuild-1.description "config subject
1606 +
1607 +body" &&
1608 + test_config format.coverFromDescription subject &&
1609 + git checkout rebuild-1 &&
1610 + git format-patch --stdout --cover-letter master >actual &&
1611 + grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
1612 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1613 + ! grep "^config subject$" actual &&
1614 + grep "^body$" actual
1615 +'
1616 +
1617 +test_expect_success 'cover letter with --cover-from-description subject' '
1618 + test_config branch.rebuild-1.description "config subject
1619 +
1620 +body" &&
1621 + git checkout rebuild-1 &&
1622 + git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
1623 + grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
1624 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1625 + ! grep "^config subject$" actual &&
1626 + grep "^body$" actual
1627 +'
1628 +
1629 +test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' '
1630 + test_config branch.rebuild-1.description "config subject
1631 +
1632 +body" &&
1633 + test_config format.coverFromDescription auto &&
1634 + git checkout rebuild-1 &&
1635 + git format-patch --stdout --cover-letter master >actual &&
1636 + grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
1637 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1638 + ! grep "^config subject$" actual &&
1639 + grep "^body$" actual
1640 +'
1641 +
1642 +test_expect_success 'cover letter with --cover-from-description auto (short subject line)' '
1643 + test_config branch.rebuild-1.description "config subject
1644 +
1645 +body" &&
1646 + git checkout rebuild-1 &&
1647 + git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
1648 + grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
1649 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1650 + ! grep "^config subject$" actual &&
1651 + grep "^body$" actual
1652 +'
1653 +
1654 +test_expect_success 'cover letter with format.coverFromDescription = auto (long subject line)' '
1655 + test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
1656 +
1657 +body" &&
1658 + test_config format.coverFromDescription auto &&
1659 + git checkout rebuild-1 &&
1660 + git format-patch --stdout --cover-letter master >actual &&
1661 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1662 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1663 + grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
1664 + grep "^body$" actual
1665 +'
1666 +
1667 +test_expect_success 'cover letter with --cover-from-description auto (long subject line)' '
1668 + test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
1669 +
1670 +body" &&
1671 + git checkout rebuild-1 &&
1672 + git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
1673 + grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
1674 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1675 + grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
1676 + grep "^body$" actual
1677 +'
1678 +
1679 +test_expect_success 'cover letter with command-line --cover-from-description overrides config' '
1680 + test_config branch.rebuild-1.description "config subject
1681 +
1682 +body" &&
1683 + test_config format.coverFromDescription none &&
1684 + git checkout rebuild-1 &&
1685 + git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
1686 + grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
1687 + ! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
1688 + ! grep "^config subject$" actual &&
1689 + grep "^body$" actual
1690 +'
1691 +
1692 test_expect_success 'cover letter using branch description (1)' '
1693 git checkout rebuild-1 &&
1694 test_config branch.rebuild-1.description hello &&
t/t9902-completion.sh
+4 -1
@@ -1548,7 +1548,10 @@ test_expect_success 'complete tree filename with metacharacters' '
1548 '
1549
1550 test_expect_success PERL 'send-email' '
1551 - test_completion "git send-email --cov" "--cover-letter " &&
1551 + test_completion "git send-email --cov" <<-\EOF &&
1552 + --cover-from-description=Z
1553 + --cover-letter Z
1554 + EOF
1555 test_completion "git send-email ma" "master "
1556 '
1557