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