git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH RFC3 12/13] send-email: --compose takes optional argument to existing file

From
Michael Witten <mfwitten@gmail.com>
Date
Apr 13, 2009, 18:23 UTC
Message-ID
<1239647037-15381-13-git-send-email-mfwitten@gmail.com>
In-Reply-To
<1239647037-15381-12-git-send-email-mfwitten@gmail.com>

Now, a user may specify an existing (in-progress) file to use as the introductory/summary email.

The file is opened for any additional editing as usual, but it is not deleted upon normal termination.

There are also a number of fixes to how the internals and temporaries are handled.

Also, it is no longer possible to pass --compose multiple times in order to edit the transformed version as well.

Also, send-email now aborts upon discovering an empty message.
Signed-off-by: Michael Witten <mfwitten@gmail.com>
---
 Documentation/git-send-email.txt |   40 +++++++--
 git-send-email.perl              |  182 ++++++++++++++++++++++++--------------
 2 files changed, 145 insertions(+), 77 deletions(-)
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 93cfb34..5ef8b12 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -55,17 +55,39 @@ The --bcc option must be repeated for each user you want on the bcc list.
 +
 The --cc option must be repeated for each user you want on the cc list.
 
---compose::
-	Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
-	introductory message for the patch series.
+--compose[=<path>]::
+	Create an introductory message for the patch series; the
+	message is opened for editing with (in order of preference)
+	`$GIT_EDITOR`, 'core.editor', `$VISUAL`, `$EDITOR`, or `vi`.
 +
-When '--compose' is used, git send-email will use the From, Subject, and
-In-Reply-To headers specified in the message. If the body of the message
-(what you type after the headers and a blank line) only contains blank
-(or GIT: prefixed) lines the summary won't be sent, but From, Subject,
-and In-Reply-To headers will be used unless they are removed.
+A path for the composition may be given. If the path exists, then
+the file at the path is opened for editing as-is. If the path
+doesn't exist, a file with default content is created at the path
+and opened for editing. If no path is specified, a new temporary
+file with default content is created and opened for editing.
 +
-Missing From or In-Reply-To headers will be prompted for.
+Upon the successful completion of send-email, all temporary files
+are automatically unlinked (deleted). However, if send-email is
+terminated by a trappable signal, then this temporary file is not
+unlinked, and the user is informed of its path.
++
+The user actually composes what will become a valid email message;
+therefore, the message must have the following form (as described in
+`RFC 822`):
++
+	<headers>
+	<blank line>
+	<body>
++
+In particular, `<headers>` must contain the "`Subject`" header. Once
+the user saves the message and quits the editor, this intermediate
+message is transformed into the final email message by removing all
+lines that begin with "`GIT:`". If the `<body>` of the final version
+is empty, then send-email aborts without sending anything.
++
+The "`From`", "`Subject`", and "`In-Reply-To`" headers are taken
+directly from the message; missing "`From`" or "`In-Reply-To`"
+headers will be prompted for.
 +
 See the CONFIGURATION section for 'sendemail.multiedit'.
 
diff --git a/git-send-email.perl b/git-send-email.perl
index 8ce9d3b..2ab76c6 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -58,10 +58,9 @@ git send-email [options] <file | directory | rev-list options >
     --bcc               <address>  * Email Bcc:
     --subject            <string>  * Email "Subject:"
     --in-reply-to    <message-id>  * Email "In-Reply-To:"; include '<' and '>'.
+    --compose             [<path>] * Open an editor for introduction.
     --annotate                     * Review each patch that will be sent in
                                      an editor.
-    --compose                      * Open an editor for introduction.
-
   Sending:
     --envelope-sender   <address>  * Email envelope sender.
     --smtp-server          <host>  * Outgoing SMTP server.
@@ -168,7 +167,7 @@ if ($@) {
 # Behavior modification variables
 my ($quiet, $dry_run) = (0, 0);
 my $format_patch;
-my $compose_filename;
+my ($compose_path, $compose_final_path);
 
 # Handle interactive edition of files.
 my $multiedit;
@@ -234,16 +233,15 @@ sub signal_handler {
 	system "stty echo";
 
 	# tmp files from --compose
-	if (defined $compose_filename) {
-		if (-e $compose_filename) {
-			print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
-		}
-		if (-e ($compose_filename . ".final")) {
-			print "'$compose_filename.final' contains the composed email.\n"
-		}
+	if (defined $compose_path and -f $compose_path) {
+		print "'$compose_path' contains an intermediate version of the email you were composing.\n";
+	}
+
+	if (defined $compose_final_path) {
+		unlink $compose_final_path if defined $compose_final_path;
 	}
 
-	exit;
+	exit 1;
 };
 
 $SIG{TERM} = \&signal_handler;
@@ -268,8 +266,8 @@ my $rc = GetOptions(
 	"smtp-ssl" => sub { $smtp_encryption = 'ssl' },
 	"smtp-encryption=s" => \$smtp_encryption,
 	"identity=s" => \$identity,
+	"compose:s" => \$compose,
 	"annotate" => \$annotate,
-	"compose" => \$compose,
 	"quiet" => \$quiet,
 	"cc-cmd=s" => \$cc_cmd,
 	"suppress-from!" => \$suppress_from,
@@ -590,60 +588,108 @@ sub get_patch_subject($) {
 	die "No subject line in $fn ?";
 }
 
-if ($compose) {
-	# Note that this does not need to be secure, but we will make a small
-	# effort to have it be unique
-	$compose_filename = ($repo ?
-		tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
-		tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
-	open(C,">",$compose_filename)
-		or die "Failed to open for writing $compose_filename: $!";
-
-
-	my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
-	my $tpl_subject = $initial_subject || '';
-	my $tpl_reply_to = $initial_reply_to || '';
-
-	print C <<EOT;
-From $tpl_sender # This line is ignored.
-GIT: Lines beginning in "GIT:" will be removed.
-GIT: Consider including an overall diffstat or table of contents
-GIT: for the patch you are writing.
-GIT:
-GIT: Clear the body content if you don't wish to send a summary.
-From: $tpl_sender
-Subject: $tpl_subject
-In-Reply-To: $tpl_reply_to
+if (defined $compose) {
 
-EOT
-	for my $f (@files) {
-		print C get_patch_subject($f);
+	# Some parameters for creating temporary files:
+
+	my $template = ".gitsendemail.msg.XXXXXX";
+	my @dir = (DIR => ($repo ? $repo->repo_path() : "."));
+	my @suffix = (SUFFIX => ".final");
+
+	# Figure out the file that the user should be editing:
+
+	my $compose_path_filehandle;
+
+	if ($compose eq '') { # if no path was given
+		($compose_path_filehandle, $compose_path) = tempfile($template, @dir)
+			or die "--compose: Could not create temporary file for the user to edit: $!\n";
+	} else {
+		$compose_path = $compose;
+		unless (-f $compose_path) {
+			open $compose_path_filehandle, ">", $compose_path
+				or die "--compose: Could not open '$compose_path' for writing default content: $!\n";
+		}
 	}
-	close(C);
+
+	# Fill in default content if necessary:
+
+	if ($compose_path_filehandle) {
+
+		# For convenience:
+
+		local *STDOUT = $compose_path_filehandle;
+
+		# Help the user out with some instruction and initial headers:
+
+		my $from = $sender || $repoauthor || $repocommitter || '';
+		my $subject = $initial_subject || '';
+		my $reply_to = $initial_reply_to || '';
+
+		print "From $from # This line is ignored.\n";
+		print "GIT:\n";
+		print "GIT: Lines beginning in 'GIT:' will be removed.\n";
+		print "GIT:\n";
+		print "GIT: Consider including an overall diffstat\n";
+		print "GIT: (git diff --stat) or table of contents\n";
+		print "GIT: (as provide below).\n";
+		print "GIT:\n";
+		print "GIT: Clear the body content if you decide not\n";
+		print "GIT: to send this message.\n";
+		print "GIT:\n";
+		print "GIT: Here are the <headers>:\n";
+		print "From: $from\n";
+		print "Subject: $subject\n";
+		print "In-Reply-To: $reply_to\n";
+		print "\n";
+		print "GIT: This is the first line of the <body>:\n";
+		print "\n";
+
+		for my $f (@files) {
+			print get_patch_subject($f);
+		}
+	}
+
+	# Do the editing:
 
 	if ($annotate) {
-		do_edit($compose_filename, @files);
+		do_edit($compose_path, @files);
 	} else {
-		do_edit($compose_filename);
+		do_edit($compose_path);
 	}
 
-	open(C2,">",$compose_filename . ".final")
-		or die "Failed to open $compose_filename.final : " . $!;
+	# Now transform the user-edited introduction into something
+	# suitable for sending via email; the user's editor may have
+	# unlinked the original file and replaced it with an entirely
+	# new one. If this be the case, then it wouldn't do just to seek
+	# to the beginning and start reading, because then only the
+	# original content would be retrieved. Consequently, the file
+	# must be reopened to be safe (note, the original filehandle is
+	# closed automatically):
+
+	unless (-f $compose_path) {
+		die "--compose: File '$compose_path' doesn't exist; aborting.\n";
+	}
+
+	open $compose_path_filehandle, "<", $compose_path
+		or die "--compose: Failed to open '$compose_path' for reading: $!";
 
-	open(C,"<",$compose_filename)
-		or die "Failed to open $compose_filename : " . $!;
+	# Create the final version:
 
-	my $need_8bit_cte = file_has_nonascii($compose_filename);
+	(my $compose_final_file, $compose_final_path) = tempfile($template, @dir, @suffix)
+		or die "--compose: Could not create temporary file for final version: $!\n";
+
+	my ($subject, $reply_to, $from);
+	my $need_8bit_cte = file_has_nonascii($compose_path);
 	my $in_body = 0;
 	my $summary_empty = 1;
-	while(<C>) {
+	while(<$compose_path_filehandle>) {
 		next if m/^GIT:/;
 		if ($in_body) {
 			$summary_empty = 0 unless (/^\n$/);
 		} elsif (/^\n$/) {
 			$in_body = 1;
 			if ($need_8bit_cte) {
-				print C2
+				print $compose_final_file
 					"MIME-Version: 1.0\n",
 					"Content-Type: text/plain; ",
 					"charset=utf-8\n",
@@ -653,12 +699,7 @@ EOT
 			$need_8bit_cte = 0;
 		} elsif (/^Subject:\s*(.+)\s*$/i) {
 			$initial_subject = $1;
-			my $subject = $initial_subject;
-			$_ = "Subject: " .
-				($subject =~ /[^[:ascii:]]/ ?
-				quote_rfc2047($subject) :
-				$subject) .
-				"\n";
+			next;
 		} elsif (/^In-Reply-To:\s*(.+)\s*$/i) {
 			$initial_reply_to = $1;
 			next;
@@ -669,15 +710,14 @@ EOT
 			print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
 			next;
 		}
-		print C2 $_;
-	}
-	close(C);
-	close(C2);
 
-	if ($summary_empty) {
-		print "Summary email is empty, skipping it\n";
-		$compose = -1;
+		print $compose_final_file $_;
 	}
+
+	$summary_empty and die "Introductory email is empty; aborting.\n";
+
+	unshift(@files, $compose_final_path);
+
 } elsif ($annotate) {
 	do_edit(@files);
 }
@@ -770,10 +810,6 @@ if (defined $smtp_server && $smtp_server ne '') {
 	$smtp_server_is_a_command = 0;
 }
 
-if ($compose && $compose > 0) {
-	@files = ($compose_filename . ".final", @files);
-}
-
 # Variables we set as part of the loop over files
 our ($message_id, %mail, $subject, $reply_to, $references, $message,
 	$needs_confirm, $message_num, $ask_default);
@@ -918,9 +954,12 @@ sub send_message
 	my $sanitized_sender = sanitize_address($sender);
 	make_message_id() unless defined($message_id);
 
+	my $has_non_ascii = ($subject =~ /[^[:ascii:]]/);
+	my $sanitized_subject = $has_non_ascii ? quote_rfc2047($subject) : $subject;
+
 	my $header = "From: $sanitized_sender
 To: $to${ccline}
-Subject: $subject
+Subject: $sanitized_subject
 Date: $date
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
@@ -1280,7 +1319,14 @@ for (my $index = 0; $index < @files; $index++) {
 cleanup_compose_files();
 
 sub cleanup_compose_files() {
-	unlink($compose_filename, $compose_filename . ".final") if $compose;
+
+	if (defined $compose_final_path) {
+		unlink $compose_final_path;
+	}
+
+	if (defined $compose_path and not $compose) {
+		unlink $compose_path;
+	}
 }
 
 $smtp->quit if $smtp;
-- 
1.6.2.2.479.g2aec
Previous: Michael WittenNext: Michael Witten
Message 13 of 39 in “I hope this will do it!”
  1. I hope this will do it!Michael Witten, Apr 13, 2009
  2. 01/13 Docs: send-email: Put options back into alphabetical orderMichael Witten, Apr 13, 2009
  3. 02/13 Docs: send-email: Refer to CONFIGURATION section for sendemail.multieditMichael Witten, Apr 13, 2009
  4. 03/13 Docs: send-email: Remove superfluous information in CONFIGURATIONMichael Witten, Apr 13, 2009
  5. 04/13 Docs: send-email: --smtp-server-port can take symbolic portsMichael Witten, Apr 13, 2009
  6. 05/13 send-email: Cleanup the usage text and docs a bitMichael Witten, Apr 13, 2009
  7. 06/13 send-email: Handle "GIT:" rather than "GIT: " during --composeMichael Witten, Apr 13, 2009
  8. 07/13 send-email: 'References:' should only reference what is sentMichael Witten, Apr 13, 2009
  9. 08/13 send-email: Remove superfluous `my $editor = ...'Michael Witten, Apr 13, 2009
  10. 09/13 send-email: Remove horrible mix of tabs and spacesMichael Witten, Apr 13, 2009
  11. 10/13 send-email: Add --sleep for email throttlingMichael Witten, Apr 13, 2009
  12. 11/13 send-email: Minor cleanup of $smtp_server usage and send_message()Michael Witten, Apr 13, 2009
  13. 12/13 send-email: --compose takes optional argument to existing fileMichael Witten, Apr 13, 2009
  14. 13/13 send-email: --compose always includes a 'GIT: ' prefixed list of patch subjectsMichael Witten, Apr 13, 2009
  15. Junio C HamanoApr 13, 2009
  16. Michael WittenApr 13, 2009
  17. Andreas EricssonApr 14, 2009
  18. Junio C HamanoApr 14, 2009
  19. Andreas EricssonApr 14, 2009
  20. Michael WittenApr 14, 2009
  21. Andreas EricssonApr 14, 2009
  22. Stephen BoydApr 13, 2009
  23. Michael WittenApr 14, 2009
  24. Michael WittenApr 14, 2009
  25. Björn SteinbrinkApr 14, 2009
  26. Junio C HamanoApr 14, 2009
  27. Junio C HamanoApr 13, 2009
  28. Michael WittenApr 13, 2009
  29. Junio C HamanoApr 14, 2009
  30. Michael WittenApr 14, 2009
  31. Junio C HamanoApr 14, 2009
  32. Michael WittenApr 14, 2009
  33. Junio C HamanoApr 13, 2009
  34. Michael WittenApr 13, 2009
  35. Michael WittenApr 13, 2009
  36. Junio C HamanoApr 14, 2009
  37. Michael WittenApr 14, 2009
  38. Junio C HamanoApr 14, 2009
  39. Michael WittenApr 14, 2009

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.