In preparation to fix <https://issues.guix.gnu.org/58813>. * gnu/packages/patches/git-header-cmd.patch: New file. * gnu/local.mk (dist_patch_DATA): Register it. * gnu/packages/version-control.scm (git) [source]: Apply patch.
		
			
				
	
	
		
			287 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
Add a '--header-cmd' to git send-email.
 | 
						|
 | 
						|
Upstream status can be tracked at:
 | 
						|
https://lore.kernel.org/git/20230423122744.4865-1-maxim.cournoyer@gmail.com/T/#t
 | 
						|
 | 
						|
diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt
 | 
						|
index 51da7088a8..92a9ebe98c 100644
 | 
						|
--- a/Documentation/config/sendemail.txt
 | 
						|
+++ b/Documentation/config/sendemail.txt
 | 
						|
@@ -61,6 +61,7 @@ sendemail.ccCmd::
 | 
						|
 sendemail.chainReplyTo::
 | 
						|
 sendemail.envelopeSender::
 | 
						|
 sendemail.from::
 | 
						|
+sendemail.headerCmd::
 | 
						|
 sendemail.signedoffbycc::
 | 
						|
 sendemail.smtpPass::
 | 
						|
 sendemail.suppresscc::
 | 
						|
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
 | 
						|
index b0f438ec99..4d2ae061f9 100644
 | 
						|
--- a/Documentation/git-send-email.txt
 | 
						|
+++ b/Documentation/git-send-email.txt
 | 
						|
@@ -320,6 +320,17 @@ Automating
 | 
						|
 	Output of this command must be single email address per line.
 | 
						|
 	Default is the value of `sendemail.ccCmd` configuration value.
 | 
						|
 
 | 
						|
+--header-cmd=<command>::
 | 
						|
+	Specify a command that is executed once per outgoing message
 | 
						|
+	and output RFC 2822 style header lines to be inserted into
 | 
						|
+	them. When the `sendemail.headerCmd` configuration variable is
 | 
						|
+	set, its value is always used. When --header-cmd is provided
 | 
						|
+	at the command line, its value takes precedence over the
 | 
						|
+	`sendemail.headerCmd` configuration variable.
 | 
						|
+
 | 
						|
+--no-header-cmd::
 | 
						|
+	Disable any header command in use.
 | 
						|
+
 | 
						|
 --[no-]chain-reply-to::
 | 
						|
 	If this is set, each email will be sent as a reply to the previous
 | 
						|
 	email sent.  If disabled with "--no-chain-reply-to", all emails after
 | 
						|
diff --git a/git-send-email.perl b/git-send-email.perl
 | 
						|
index 66c9171109..22a64e608f 100755
 | 
						|
--- a/git-send-email.perl
 | 
						|
+++ b/git-send-email.perl
 | 
						|
@@ -87,8 +87,10 @@ sub usage {
 | 
						|
 
 | 
						|
   Automating:
 | 
						|
     --identity              <str>  * Use the sendemail.<id> options.
 | 
						|
-    --to-cmd                <str>  * Email To: via `<str> \$patch_path`
 | 
						|
-    --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
 | 
						|
+    --to-cmd                <str>  * Email To: via `<str> \$patch_path`.
 | 
						|
+    --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`.
 | 
						|
+    --header-cmd            <str>  * Add headers via `<str> \$patch_path`.
 | 
						|
+    --no-header-cmd                * Disable any header command in use.
 | 
						|
     --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, misc-by, all.
 | 
						|
     --[no-]cc-cover                * Email Cc: addresses in the cover letter.
 | 
						|
     --[no-]to-cover                * Email To: addresses in the cover letter.
 | 
						|
@@ -202,7 +204,7 @@ sub format_2822_time {
 | 
						|
 	$author,$sender,$smtp_authpass,$annotate,$compose,$time);
 | 
						|
 # Things we either get from config, *or* are overridden on the
 | 
						|
 # command-line.
 | 
						|
-my ($no_cc, $no_to, $no_bcc, $no_identity);
 | 
						|
+my ($no_cc, $no_to, $no_bcc, $no_identity, $no_header_cmd);
 | 
						|
 my (@config_to, @getopt_to);
 | 
						|
 my (@config_cc, @getopt_cc);
 | 
						|
 my (@config_bcc, @getopt_bcc);
 | 
						|
@@ -269,7 +271,7 @@ sub do_edit {
 | 
						|
 # Variables with corresponding config settings
 | 
						|
 my ($suppress_from, $signed_off_by_cc);
 | 
						|
 my ($cover_cc, $cover_to);
 | 
						|
-my ($to_cmd, $cc_cmd);
 | 
						|
+my ($to_cmd, $cc_cmd, $header_cmd);
 | 
						|
 my ($smtp_server, $smtp_server_port, @smtp_server_options);
 | 
						|
 my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
 | 
						|
 my ($batch_size, $relogin_delay);
 | 
						|
@@ -318,6 +320,7 @@ sub do_edit {
 | 
						|
     "tocmd" => \$to_cmd,
 | 
						|
     "cc" => \@config_cc,
 | 
						|
     "cccmd" => \$cc_cmd,
 | 
						|
+    "headercmd" => \$header_cmd,
 | 
						|
     "aliasfiletype" => \$aliasfiletype,
 | 
						|
     "bcc" => \@config_bcc,
 | 
						|
     "suppresscc" => \@suppress_cc,
 | 
						|
@@ -519,6 +522,8 @@ sub config_regexp {
 | 
						|
 		    "compose" => \$compose,
 | 
						|
 		    "quiet" => \$quiet,
 | 
						|
 		    "cc-cmd=s" => \$cc_cmd,
 | 
						|
+		    "header-cmd=s" => \$header_cmd,
 | 
						|
+		    "no-header-cmd" => \$no_header_cmd,
 | 
						|
 		    "suppress-from!" => \$suppress_from,
 | 
						|
 		    "no-suppress-from" => sub {$suppress_from = 0},
 | 
						|
 		    "suppress-cc=s" => \@suppress_cc,
 | 
						|
@@ -1780,16 +1785,16 @@ sub process_file {
 | 
						|
 	$subject = $initial_subject;
 | 
						|
 	$message = "";
 | 
						|
 	$message_num++;
 | 
						|
-	# First unfold multiline header fields
 | 
						|
+	# Retrieve and unfold header fields.
 | 
						|
+	my @header_lines = ();
 | 
						|
 	while(<$fh>) {
 | 
						|
 		last if /^\s*$/;
 | 
						|
-		if (/^\s+\S/ and @header) {
 | 
						|
-			chomp($header[$#header]);
 | 
						|
-			s/^\s+/ /;
 | 
						|
-			$header[$#header] .= $_;
 | 
						|
-	    } else {
 | 
						|
-			push(@header, $_);
 | 
						|
-		}
 | 
						|
+		push(@header_lines, $_);
 | 
						|
+	}
 | 
						|
+	@header = unfold_headers(@header_lines);
 | 
						|
+	# Add computed headers, if applicable.
 | 
						|
+	unless ($no_header_cmd || ! $header_cmd) {
 | 
						|
+		push @header, invoke_header_cmd($header_cmd, $t);
 | 
						|
 	}
 | 
						|
 	# Now parse the header
 | 
						|
 	foreach(@header) {
 | 
						|
@@ -2021,15 +2026,63 @@ sub process_file {
 | 
						|
 	}
 | 
						|
 }
 | 
						|
 
 | 
						|
+# Execute a command and return its output lines as an array.  Blank
 | 
						|
+# lines which do not appear at the end of the output are reported as
 | 
						|
+# errors.
 | 
						|
+sub execute_cmd {
 | 
						|
+	my ($prefix, $cmd, $file) = @_;
 | 
						|
+	my @lines = ();
 | 
						|
+	my $seen_blank_line = 0;
 | 
						|
+	open my $fh, "-|", "$cmd \Q$file\E"
 | 
						|
+		or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd);
 | 
						|
+	while (my $line = <$fh>) {
 | 
						|
+		die sprintf(__("(%s) Malformed output from '%s'"), $prefix, $cmd)
 | 
						|
+		    if $seen_blank_line;
 | 
						|
+		if ($line =~ /^$/) {
 | 
						|
+			$seen_blank_line = $line =~ /^$/;
 | 
						|
+			next;
 | 
						|
+		}
 | 
						|
+		push @lines, $line;
 | 
						|
+	}
 | 
						|
+	close $fh
 | 
						|
+	    or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
 | 
						|
+	return @lines;
 | 
						|
+}
 | 
						|
+
 | 
						|
+# Process headers lines, unfolding multiline headers as defined by RFC
 | 
						|
+# 2822.
 | 
						|
+sub unfold_headers {
 | 
						|
+	my @headers;
 | 
						|
+	foreach(@_) {
 | 
						|
+		last if /^\s*$/;
 | 
						|
+		if (/^\s+\S/ and @headers) {
 | 
						|
+			chomp($headers[$#headers]);
 | 
						|
+			s/^\s+/ /;
 | 
						|
+			$headers[$#headers] .= $_;
 | 
						|
+		} else {
 | 
						|
+			push(@headers, $_);
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+	return @headers;
 | 
						|
+}
 | 
						|
+
 | 
						|
+# Invoke the provided CMD with FILE as an argument, which should
 | 
						|
+# output RFC 2822 email headers. Fold multiline headers and return the
 | 
						|
+# headers as an array.
 | 
						|
+sub invoke_header_cmd {
 | 
						|
+	my ($cmd, $file) = @_;
 | 
						|
+	my @lines = execute_cmd("header-cmd", $header_cmd, $file);
 | 
						|
+	return unfold_headers(@lines);
 | 
						|
+}
 | 
						|
+
 | 
						|
 # Execute a command (e.g. $to_cmd) to get a list of email addresses
 | 
						|
 # and return a results array
 | 
						|
 sub recipients_cmd {
 | 
						|
 	my ($prefix, $what, $cmd, $file) = @_;
 | 
						|
-
 | 
						|
+	my @lines = ();
 | 
						|
 	my @addresses = ();
 | 
						|
-	open my $fh, "-|", "$cmd \Q$file\E"
 | 
						|
-	    or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd);
 | 
						|
-	while (my $address = <$fh>) {
 | 
						|
+	@lines = execute_cmd($prefix, $cmd, $file);
 | 
						|
+	for my $address (@lines) {
 | 
						|
 		$address =~ s/^\s*//g;
 | 
						|
 		$address =~ s/\s*$//g;
 | 
						|
 		$address = sanitize_address($address);
 | 
						|
@@ -2038,8 +2091,6 @@ sub recipients_cmd {
 | 
						|
 		printf(__("(%s) Adding %s: %s from: '%s'\n"),
 | 
						|
 		       $prefix, $what, $address, $cmd) unless $quiet;
 | 
						|
 		}
 | 
						|
-	close $fh
 | 
						|
-	    or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
 | 
						|
 	return @addresses;
 | 
						|
 }
 | 
						|
 
 | 
						|
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
 | 
						|
index 6520346246..6519eea1ed 100755
 | 
						|
--- a/t/t9001-send-email.sh
 | 
						|
+++ b/t/t9001-send-email.sh
 | 
						|
@@ -374,13 +374,16 @@ test_expect_success $PREREQ,!AUTOIDENT 'broken implicit ident aborts send-email'
 | 
						|
 	)
 | 
						|
 '
 | 
						|
 
 | 
						|
-test_expect_success $PREREQ 'setup tocmd and cccmd scripts' '
 | 
						|
+test_expect_success $PREREQ 'setup cmd scripts' '
 | 
						|
 	write_script tocmd-sed <<-\EOF &&
 | 
						|
 	sed -n -e "s/^tocmd--//p" "$1"
 | 
						|
 	EOF
 | 
						|
-	write_script cccmd-sed <<-\EOF
 | 
						|
+	write_script cccmd-sed <<-\EOF &&
 | 
						|
 	sed -n -e "s/^cccmd--//p" "$1"
 | 
						|
 	EOF
 | 
						|
+	write_script headercmd-sed <<-\EOF
 | 
						|
+	sed -n -e "s/^headercmd--//p" "$1"
 | 
						|
+	EOF
 | 
						|
 '
 | 
						|
 
 | 
						|
 test_expect_success $PREREQ 'tocmd works' '
 | 
						|
@@ -410,6 +413,70 @@ test_expect_success $PREREQ 'cccmd works' '
 | 
						|
 	grep "^	cccmd@example.com" msgtxt1
 | 
						|
 '
 | 
						|
 
 | 
						|
+test_expect_success $PREREQ 'headercmd works' '
 | 
						|
+	clean_fake_sendmail &&
 | 
						|
+	cp $patches headercmd.patch &&
 | 
						|
+	echo "headercmd--X-Debbugs-CC: dummy@example.com" >>headercmd.patch &&
 | 
						|
+	git send-email \
 | 
						|
+		--from="Example <nobody@example.com>" \
 | 
						|
+		--to=nobody@example.com \
 | 
						|
+		--header-cmd=./headercmd-sed \
 | 
						|
+		--smtp-server="$(pwd)/fake.sendmail" \
 | 
						|
+		headercmd.patch \
 | 
						|
+		&&
 | 
						|
+	grep "^X-Debbugs-CC: dummy@example.com" msgtxt1
 | 
						|
+'
 | 
						|
+
 | 
						|
+test_expect_success $PREREQ '--no-header-cmd works' '
 | 
						|
+	clean_fake_sendmail &&
 | 
						|
+	cp $patches headercmd.patch &&
 | 
						|
+	echo "headercmd--X-Debbugs-CC: dummy@example.com" >>headercmd.patch &&
 | 
						|
+	git send-email \
 | 
						|
+		--from="Example <nobody@example.com>" \
 | 
						|
+		--to=nobody@example.com \
 | 
						|
+		--header-cmd=./headercmd-sed \
 | 
						|
+                --no-header-cmd \
 | 
						|
+		--smtp-server="$(pwd)/fake.sendmail" \
 | 
						|
+		headercmd.patch \
 | 
						|
+		&&
 | 
						|
+	! grep "^X-Debbugs-CC: dummy@example.com" msgtxt1
 | 
						|
+'
 | 
						|
+
 | 
						|
+test_expect_success $PREREQ 'multiline fields are correctly unfolded' '
 | 
						|
+	clean_fake_sendmail &&
 | 
						|
+	cp $patches headercmd.patch &&
 | 
						|
+	write_script headercmd-multiline <<-\EOF &&
 | 
						|
+	echo "X-Debbugs-CC: someone@example.com
 | 
						|
+FoldedField: This is a tale
 | 
						|
+ best told using
 | 
						|
+ multiple lines."
 | 
						|
+	EOF
 | 
						|
+	git send-email \
 | 
						|
+		--from="Example <nobody@example.com>" \
 | 
						|
+		--to=nobody@example.com \
 | 
						|
+		--header-cmd=./headercmd-multiline \
 | 
						|
+		--smtp-server="$(pwd)/fake.sendmail" \
 | 
						|
+		headercmd.patch &&
 | 
						|
+	grep "^FoldedField: This is a tale best told using multiple lines.$" msgtxt1
 | 
						|
+'
 | 
						|
+
 | 
						|
+# Blank lines in the middle of the output of a command are invalid.
 | 
						|
+test_expect_success $PREREQ 'malform output reported on blank lines in command output' '
 | 
						|
+	clean_fake_sendmail &&
 | 
						|
+	cp $patches headercmd.patch &&
 | 
						|
+	write_script headercmd-malformed-output <<-\EOF &&
 | 
						|
+	echo "X-Debbugs-CC: someone@example.com
 | 
						|
+
 | 
						|
+SomeOtherField: someone-else@example.com"
 | 
						|
+	EOF
 | 
						|
+	! git send-email \
 | 
						|
+		--from="Example <nobody@example.com>" \
 | 
						|
+		--to=nobody@example.com \
 | 
						|
+		--header-cmd=./headercmd-malformed-output \
 | 
						|
+		--smtp-server="$(pwd)/fake.sendmail" \
 | 
						|
+		headercmd.patch
 | 
						|
+'
 | 
						|
+
 | 
						|
 test_expect_success $PREREQ 'reject long lines' '
 | 
						|
 	z8=zzzzzzzz &&
 | 
						|
 	z64=$z8$z8$z8$z8$z8$z8$z8$z8 &&
 |