send-email: add --header-cmd, --no-header-cmd options
Sometimes, adding a header different than CC or TO is desirable; for example, when using Debbugs, it is best to use 'X-Debbugs-Cc' headers to keep people in CC; this is an example use case enabled by the new '--header-cmd' option. The header unfolding logic is extracted to a subroutine so that it can be reused; a test is added for coverage. Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Maxim Cournoyer committed
May 1, 2023 at 10:38 UTC
ba92106e93c66e41af5180ac1b6ad0f959f72bb4
4 files changed
+107
-14
Documentation/config/sendemail.txt
+1
@@ -61,6 +61,7 @@ sendemail.ccCmd::
61
sendemail.chainReplyTo::
62
sendemail.envelopeSender::
63
sendemail.from::
64
+sendemail.headerCmd::
65
sendemail.signedoffbycc::
66
sendemail.smtpPass::
67
sendemail.suppresscc::
Documentation/git-send-email.txt
+11
@@ -320,6 +320,17 @@ Automating
320
Output of this command must be single email address per line.
321
Default is the value of `sendemail.ccCmd` configuration value.
322
323
+--header-cmd=<command>::
324
+ Specify a command that is executed once per outgoing message
325
+ and output RFC 2822 style header lines to be inserted into
326
+ them. When the `sendemail.headerCmd` configuration variable is
327
+ set, its value is always used. When --header-cmd is provided
328
+ at the command line, its value takes precedence over the
329
+ `sendemail.headerCmd` configuration variable.
330
+
331
+--no-header-cmd::
332
+ Disable any header command in use.
333
+
334
--[no-]chain-reply-to::
335
If this is set, each email will be sent as a reply to the previous
336
email sent. If disabled with "--no-chain-reply-to", all emails after
git-send-email.perl
+43
-12
@@ -87,8 +87,10 @@ git send-email --dump-aliases
87
88
Automating:
89
--identity <str> * Use the sendemail.<id> options.
90
- --to-cmd <str> * Email To: via `<str> \$patch_path`
91
- --cc-cmd <str> * Email Cc: via `<str> \$patch_path`
90
+ --to-cmd <str> * Email To: via `<str> \$patch_path`.
91
+ --cc-cmd <str> * Email Cc: via `<str> \$patch_path`.
92
+ --header-cmd <str> * Add headers via `<str> \$patch_path`.
93
+ --no-header-cmd * Disable any header command in use.
94
--suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, misc-by, all.
95
--[no-]cc-cover * Email Cc: addresses in the cover letter.
96
--[no-]to-cover * Email To: addresses in the cover letter.
@@ -202,7 +204,7 @@ my (@to,@cc,@xh,$envelope_sender,
204
$author,$sender,$smtp_authpass,$annotate,$compose,$time);
205
# Things we either get from config, *or* are overridden on the
206
# command-line.
205
-my ($no_cc, $no_to, $no_bcc, $no_identity);
207
+my ($no_cc, $no_to, $no_bcc, $no_identity, $no_header_cmd);
208
my (@config_to, @getopt_to);
209
my (@config_cc, @getopt_cc);
210
my (@config_bcc, @getopt_bcc);
@@ -269,7 +271,7 @@ sub do_edit {
271
# Variables with corresponding config settings
272
my ($suppress_from, $signed_off_by_cc);
273
my ($cover_cc, $cover_to);
272
-my ($to_cmd, $cc_cmd);
274
+my ($to_cmd, $cc_cmd, $header_cmd);
275
my ($smtp_server, $smtp_server_port, @smtp_server_options);
276
my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
277
my ($batch_size, $relogin_delay);
@@ -318,6 +320,7 @@ my %config_settings = (
320
"tocmd" => \$to_cmd,
321
"cc" => \@config_cc,
322
"cccmd" => \$cc_cmd,
323
+ "headercmd" => \$header_cmd,
324
"aliasfiletype" => \$aliasfiletype,
325
"bcc" => \@config_bcc,
326
"suppresscc" => \@suppress_cc,
@@ -519,6 +522,8 @@ my %options = (
522
"compose" => \$compose,
523
"quiet" => \$quiet,
524
"cc-cmd=s" => \$cc_cmd,
525
+ "header-cmd=s" => \$header_cmd,
526
+ "no-header-cmd" => \$no_header_cmd,
527
"suppress-from!" => \$suppress_from,
528
"no-suppress-from" => sub {$suppress_from = 0},
529
"suppress-cc=s" => \@suppress_cc,
@@ -1780,16 +1785,16 @@ sub process_file {
1785
$subject = $initial_subject;
1786
$message = "";
1787
$message_num++;
1783
- # First unfold multiline header fields
1788
+ # Retrieve and unfold header fields.
1789
+ my @header_lines = ();
1790
while(<$fh>) {
1791
last if /^\s*$/;
1786
- if (/^\s+\S/ and @header) {
1787
- chomp($header[$#header]);
1788
- s/^\s+/ /;
1789
- $header[$#header] .= $_;
1790
- } else {
1791
- push(@header, $_);
1792
- }
1792
+ push(@header_lines, $_);
1793
+ }
1794
+ @header = unfold_headers(@header_lines);
1795
+ # Add computed headers, if applicable.
1796
+ unless ($no_header_cmd || ! $header_cmd) {
1797
+ push @header, invoke_header_cmd($header_cmd, $t);
1798
}
1799
# Now parse the header
1800
foreach(@header) {
@@ -2036,6 +2041,32 @@ sub execute_cmd {
2041
return @lines;
2042
}
2043
2044
+# Process headers lines, unfolding multiline headers as defined by RFC
2045
+# 2822.
2046
+sub unfold_headers {
2047
+ my @headers;
2048
+ foreach(@_) {
2049
+ last if /^\s*$/;
2050
+ if (/^\s+\S/ and @headers) {
2051
+ chomp($headers[$#headers]);
2052
+ s/^\s+/ /;
2053
+ $headers[$#headers] .= $_;
2054
+ } else {
2055
+ push(@headers, $_);
2056
+ }
2057
+ }
2058
+ return @headers;
2059
+}
2060
+
2061
+# Invoke the provided CMD with FILE as an argument, which should
2062
+# output RFC 2822 email headers. Fold multiline headers and return the
2063
+# headers as an array.
2064
+sub invoke_header_cmd {
2065
+ my ($cmd, $file) = @_;
2066
+ my @lines = execute_cmd("header-cmd", $header_cmd, $file);
2067
+ return unfold_headers(@lines);
2068
+}
2069
+
2070
# Execute a command (e.g. $to_cmd) to get a list of email addresses
2071
# and return a results array
2072
sub recipients_cmd {
t/t9001-send-email.sh
+52
-2
@@ -374,13 +374,16 @@ test_expect_success $PREREQ,!AUTOIDENT 'broken implicit ident aborts send-email'
374
)
375
'
376
377
-test_expect_success $PREREQ 'setup tocmd and cccmd scripts' '
377
+test_expect_success $PREREQ 'setup cmd scripts' '
378
write_script tocmd-sed <<-\EOF &&
379
sed -n -e "s/^tocmd--//p" "$1"
380
EOF
381
- write_script cccmd-sed <<-\EOF
381
+ write_script cccmd-sed <<-\EOF &&
382
sed -n -e "s/^cccmd--//p" "$1"
383
EOF
384
+ write_script headercmd-sed <<-\EOF
385
+ sed -n -e "s/^headercmd--//p" "$1"
386
+ EOF
387
'
388
389
test_expect_success $PREREQ 'tocmd works' '
@@ -410,6 +413,53 @@ test_expect_success $PREREQ 'cccmd works' '
413
grep "^ cccmd@example.com" msgtxt1
414
'
415
416
+test_expect_success $PREREQ 'headercmd works' '
417
+ clean_fake_sendmail &&
418
+ cp $patches headercmd.patch &&
419
+ echo "headercmd--X-Debbugs-CC: dummy@example.com" >>headercmd.patch &&
420
+ git send-email \
421
+ --from="Example <nobody@example.com>" \
422
+ --to=nobody@example.com \
423
+ --header-cmd=./headercmd-sed \
424
+ --smtp-server="$(pwd)/fake.sendmail" \
425
+ headercmd.patch \
426
+ &&
427
+ grep "^X-Debbugs-CC: dummy@example.com" msgtxt1
428
+'
429
+
430
+test_expect_success $PREREQ '--no-header-cmd works' '
431
+ clean_fake_sendmail &&
432
+ cp $patches headercmd.patch &&
433
+ echo "headercmd--X-Debbugs-CC: dummy@example.com" >>headercmd.patch &&
434
+ git send-email \
435
+ --from="Example <nobody@example.com>" \
436
+ --to=nobody@example.com \
437
+ --header-cmd=./headercmd-sed \
438
+ --no-header-cmd \
439
+ --smtp-server="$(pwd)/fake.sendmail" \
440
+ headercmd.patch \
441
+ &&
442
+ ! grep "^X-Debbugs-CC: dummy@example.com" msgtxt1
443
+'
444
+
445
+test_expect_success $PREREQ 'multiline fields are correctly unfolded' '
446
+ clean_fake_sendmail &&
447
+ cp $patches headercmd.patch &&
448
+ write_script headercmd-multiline <<-\EOF &&
449
+ echo "X-Debbugs-CC: someone@example.com
450
+FoldedField: This is a tale
451
+ best told using
452
+ multiple lines."
453
+ EOF
454
+ git send-email \
455
+ --from="Example <nobody@example.com>" \
456
+ --to=nobody@example.com \
457
+ --header-cmd=./headercmd-multiline \
458
+ --smtp-server="$(pwd)/fake.sendmail" \
459
+ headercmd.patch &&
460
+ grep "^FoldedField: This is a tale best told using multiple lines.$" msgtxt1
461
+'
462
+
463
test_expect_success $PREREQ 'reject long lines' '
464
z8=zzzzzzzz &&
465
z64=$z8$z8$z8$z8$z8$z8$z8$z8 &&