@matheusbsbs / matheus-bagliano / commits / c1f2aa45b7
Fork 0

send-email: add --confirm option and configuration setting

send-email violates the principle of least surprise by automatically cc'ing additional recipients without confirming this with the user. This patch teaches send-email a --confirm option. It takes the following values: --confirm=always always confirm before sending --confirm=never never confirm before sending --confirm=cc confirm before sending when send-email has automatically added addresses from the patch to the Cc list --confirm=compose confirm before sending the first message when using --compose. (Needed to maintain backwards compatibility with existing behavior.) --confirm=auto 'cc' + 'compose' If sendemail.confirm is unconfigured, the option defaults to 'compose' if any suppress-Cc related options have been used, otherwise it defaults to 'auto'. Unfortunately, it is impossible to introduce this patch such that it helps new users without potentially annoying some existing users. We attempt to mitigate the latter by: * Allowing the user to set 'git config sendemail.confirm never' * Allowing the user to say 'all' after the first prompt to not be prompted on remaining emails during the same invocation. * Telling the user about the 'sendemail.confirm' setting if it is unconfigured whenever we prompt due to Cc before sending. * Only prompting if no --suppress related options have been passed, as using such an option is likely to indicate an experienced send-email user. There is a slight fib in message informing the user of the sendemail.confirm setting and this is intentional. Setting 'auto' differs from leaving sendemail.confirm unset in two ways: 1) 'auto' obviously squelches the informational message; 2) 'auto' prompts when the Cc list has been expanded even in the presence of a --suppress related option, where leaving sendemail.confirm unset does not. This is intentional to keep the message simple, and to avoid adding another sendemail.confirm value ('auto-except-suppress'?). Signed-off-by: Jay Soffian <jaysoffian@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jay Soffian committed Mar 2, 2009 at 23:52 UTC c1f2aa45b7bdb82c4378443ae23ad9625e782fe2
3 files changed +174 -39
Documentation/git-send-email.txt
+21
@@ -199,6 +199,22 @@ specified, as well as 'body' if --no-signed-off-cc is specified.
199 Administering
200 ~~~~~~~~~~~~~
201
202 +--confirm::
203 + Confirm just before sending:
204 ++
205 +--
206 +- 'always' will always confirm before sending
207 +- 'never' will never confirm before sending
208 +- 'cc' will confirm before sending when send-email has automatically
209 + added addresses from the patch to the Cc list
210 +- 'compose' will confirm before sending the first message when using --compose.
211 +- 'auto' is equivalent to 'cc' + 'compose'
212 +--
213 ++
214 +Default is the value of 'sendemail.confirm' configuration value; if that
215 +is unspecified, default to 'auto' unless any of the suppress options
216 +have been specified, in which case default to 'compose'.
217 +
218 --dry-run::
219 Do everything except actually send the emails.
220
@@ -242,6 +258,11 @@ sendemail.multiedit::
258 summary when '--compose' is used). If false, files will be edited one
259 after the other, spawning a new editor each time.
260
261 +sendemail.confirm::
262 + Sets the default for whether to confirm before sending. Must be
263 + one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm'
264 + in the previous section for the meaning of these values.
265 +
266
267 Author
268 ------
git-send-email.perl
+61 -23
@@ -75,6 +75,8 @@ git send-email [options] <file | directory | rev-list options >
75 --[no-]thread * Use In-Reply-To: field. Default on.
76
77 Administering:
78 + --confirm <str> * Confirm recipients before sending;
79 + auto, cc, compose, always, or never.
80 --quiet * Output one line of info per email.
81 --dry-run * Don't actually send the emails.
82 --[no-]validate * Perform patch sanity checks. Default on.
@@ -181,7 +183,7 @@ sub do_edit {
183 my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
184 my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
185 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
184 -my ($validate);
186 +my ($validate, $confirm);
187 my (@suppress_cc);
188
189 my %config_bool_settings = (
@@ -207,6 +209,7 @@ my %config_settings = (
209 "suppresscc" => \@suppress_cc,
210 "envelopesender" => \$envelope_sender,
211 "multiedit" => \$multiedit,
212 + "confirm" => \$confirm,
213 );
214
215 # Handle Uncouth Termination
@@ -258,6 +261,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
261 "suppress-from!" => \$suppress_from,
262 "suppress-cc=s" => \@suppress_cc,
263 "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
264 + "confirm=s" => \$confirm,
265 "dry-run" => \$dry_run,
266 "envelope-sender=s" => \$envelope_sender,
267 "thread!" => \$thread,
@@ -346,6 +350,14 @@ if ($suppress_cc{'body'}) {
350 delete $suppress_cc{'body'};
351 }
352
353 +# Set confirm's default value
354 +my $confirm_unconfigured = !defined $confirm;
355 +if ($confirm_unconfigured) {
356 + $confirm = scalar %suppress_cc ? 'compose' : 'auto';
357 +};
358 +die "Unknown --confirm setting: '$confirm'\n"
359 + unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
360 +
361 # Debugging, print out the suppressions.
362 if (0) {
363 print "suppressions:\n";
@@ -663,25 +675,13 @@ if (!defined $smtp_server) {
675 $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
676 }
677
666 -if ($compose) {
667 - while (1) {
668 - $_ = $term->readline("Send this email? (y|n) ");
669 - last if defined $_;
670 - print "\n";
671 - }
672 -
673 - if (uc substr($_,0,1) ne 'Y') {
674 - cleanup_compose_files();
675 - exit(0);
676 - }
677 -
678 - if ($compose > 0) {
679 - @files = ($compose_filename . ".final", @files);
680 - }
678 +if ($compose && $compose > 0) {
679 + @files = ($compose_filename . ".final", @files);
680 }
681
682 # Variables we set as part of the loop over files
684 -our ($message_id, %mail, $subject, $reply_to, $references, $message);
683 +our ($message_id, %mail, $subject, $reply_to, $references, $message,
684 + $needs_confirm, $message_num);
685
686 sub extract_valid_address {
687 my $address = shift;
@@ -837,6 +837,37 @@ X-Mailer: git-send-email $gitversion
837 unshift (@sendmail_parameters,
838 '-f', $raw_from) if(defined $envelope_sender);
839
840 + if ($needs_confirm && !$dry_run) {
841 + print "\n$header\n";
842 + if ($needs_confirm eq "inform") {
843 + $confirm_unconfigured = 0; # squelch this message for the rest of this run
844 + print " The Cc list above has been expanded by additional\n";
845 + print " addresses found in the patch commit message. By default\n";
846 + print " send-email prompts before sending whenever this occurs.\n";
847 + print " This behavior is controlled by the sendemail.confirm\n";
848 + print " configuration setting.\n";
849 + print "\n";
850 + print " For additional information, run 'git send-email --help'.\n";
851 + print " To retain the current behavior, but squelch this message,\n";
852 + print " run 'git config --global sendemail.confirm auto'.\n\n";
853 + }
854 + while (1) {
855 + chomp ($_ = $term->readline(
856 + "Send this email? ([y]es|[n]o|[q]uit|[a]ll): "
857 + ));
858 + last if /^(?:yes|y|no|n|quit|q|all|a)/i;
859 + print "\n";
860 + }
861 + if (/^n/i) {
862 + return;
863 + } elsif (/^q/i) {
864 + cleanup_compose_files();
865 + exit(0);
866 + } elsif (/^a/i) {
867 + $confirm = 'never';
868 + }
869 + }
870 +
871 if ($dry_run) {
872 # We don't want to send the email.
873 } elsif ($smtp_server =~ m#^/#) {
@@ -935,6 +966,7 @@ X-Mailer: git-send-email $gitversion
966 $reply_to = $initial_reply_to;
967 $references = $initial_reply_to || '';
968 $subject = $initial_subject;
969 +$message_num = 0;
970
971 foreach my $t (@files) {
972 open(F,"<",$t) or die "can't open file $t";
@@ -943,11 +975,12 @@ foreach my $t (@files) {
975 my $author_encoding;
976 my $has_content_type;
977 my $body_encoding;
946 - @cc = @initial_cc;
978 + @cc = ();
979 @xh = ();
980 my $input_format = undef;
981 my @header = ();
982 $message = "";
983 + $message_num++;
984 # First unfold multiline header fields
985 while(<F>) {
986 last if /^\s*$/;
@@ -1080,6 +1113,14 @@ foreach my $t (@files) {
1113 }
1114 }
1115
1116 + $needs_confirm = (
1117 + $confirm eq "always" or
1118 + ($confirm =~ /^(?:auto|cc)$/ && @cc) or
1119 + ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
1120 + $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
1121 +
1122 + @cc = (@initial_cc, @cc);
1123 +
1124 send_message();
1125
1126 # set up for the next message
@@ -1094,13 +1135,10 @@ foreach my $t (@files) {
1135 $message_id = undef;
1136 }
1137
1097 -if ($compose) {
1098 - cleanup_compose_files();
1099 -}
1138 +cleanup_compose_files();
1139
1140 sub cleanup_compose_files() {
1102 - unlink($compose_filename, $compose_filename . ".final");
1103 -
1141 + unlink($compose_filename, $compose_filename . ".final") if $compose;
1142 }
1143
1144 $smtp->quit if $smtp;
t/t9001-send-email.sh
+92 -16
@@ -35,6 +35,47 @@ test_expect_success 'Extract patches' '
35 patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
36 '
37
38 +# Test no confirm early to ensure remaining tests will not hang
39 +test_no_confirm () {
40 + rm -f no_confirm_okay
41 + echo n | \
42 + GIT_SEND_EMAIL_NOTTY=1 \
43 + git send-email \
44 + --from="Example <from@example.com>" \
45 + --to=nobody@example.com \
46 + --smtp-server="$(pwd)/fake.sendmail" \
47 + $@ \
48 + $patches > stdout &&
49 + test_must_fail grep "Send this email" stdout &&
50 + > no_confirm_okay
51 +}
52 +
53 +# Exit immediately to prevent hang if a no-confirm test fails
54 +check_no_confirm () {
55 + test -f no_confirm_okay || {
56 + say 'No confirm test failed; skipping remaining tests to prevent hanging'
57 + test_done
58 + }
59 +}
60 +
61 +test_expect_success 'No confirm with --suppress-cc' '
62 + test_no_confirm --suppress-cc=sob
63 +'
64 +check_no_confirm
65 +
66 +test_expect_success 'No confirm with --confirm=never' '
67 + test_no_confirm --confirm=never
68 +'
69 +check_no_confirm
70 +
71 +# leave sendemail.confirm set to never after this so that none of the
72 +# remaining tests prompt unintentionally.
73 +test_expect_success 'No confirm with sendemail.confirm=never' '
74 + git config sendemail.confirm never &&
75 + test_no_confirm --compose --subject=foo
76 +'
77 +check_no_confirm
78 +
79 test_expect_success 'Send patches' '
80 git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
81 '
@@ -175,15 +216,13 @@ test_set_editor "$(pwd)/fake-editor"
216
217 test_expect_success '--compose works' '
218 clean_fake_sendmail &&
178 - echo y | \
179 - GIT_SEND_EMAIL_NOTTY=1 \
180 - git send-email \
181 - --compose --subject foo \
182 - --from="Example <nobody@example.com>" \
183 - --to=nobody@example.com \
184 - --smtp-server="$(pwd)/fake.sendmail" \
185 - $patches \
186 - 2>errors
219 + git send-email \
220 + --compose --subject foo \
221 + --from="Example <nobody@example.com>" \
222 + --to=nobody@example.com \
223 + --smtp-server="$(pwd)/fake.sendmail" \
224 + $patches \
225 + 2>errors
226 '
227
228 test_expect_success 'first message is compose text' '
@@ -375,15 +414,56 @@ test_expect_success '--suppress-cc=cc' '
414 test_suppression cc
415 '
416
417 +test_confirm () {
418 + echo y | \
419 + GIT_SEND_EMAIL_NOTTY=1 \
420 + git send-email \
421 + --from="Example <nobody@example.com>" \
422 + --to=nobody@example.com \
423 + --smtp-server="$(pwd)/fake.sendmail" \
424 + $@ \
425 + $patches | grep "Send this email"
426 +}
427 +
428 +test_expect_success '--confirm=always' '
429 + test_confirm --confirm=always --suppress-cc=all
430 +'
431 +
432 +test_expect_success '--confirm=auto' '
433 + test_confirm --confirm=auto
434 +'
435 +
436 +test_expect_success '--confirm=cc' '
437 + test_confirm --confirm=cc
438 +'
439 +
440 +test_expect_success '--confirm=compose' '
441 + test_confirm --confirm=compose --compose
442 +'
443 +
444 +test_expect_success 'confirm by default (due to cc)' '
445 + CONFIRM=$(git config --get sendemail.confirm) &&
446 + git config --unset sendemail.confirm &&
447 + test_confirm &&
448 + git config sendemail.confirm $CONFIRM
449 +'
450 +
451 +test_expect_success 'confirm by default (due to --compose)' '
452 + CONFIRM=$(git config --get sendemail.confirm) &&
453 + git config --unset sendemail.confirm &&
454 + test_confirm --suppress-cc=all --compose
455 + ret="$?"
456 + git config sendemail.confirm ${CONFIRM:-never}
457 + test $ret = "0"
458 +'
459 +
460 test_expect_success '--compose adds MIME for utf8 body' '
461 clean_fake_sendmail &&
462 (echo "#!$SHELL_PATH" &&
463 echo "echo utf8 body: àéìöú >>\"\$1\""
464 ) >fake-editor-utf8 &&
465 chmod +x fake-editor-utf8 &&
384 - echo y | \
466 GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
386 - GIT_SEND_EMAIL_NOTTY=1 \
467 git send-email \
468 --compose --subject foo \
469 --from="Example <nobody@example.com>" \
@@ -405,9 +485,7 @@ test_expect_success '--compose respects user mime type' '
485 echo " echo utf8 body: àéìöú) >\"\$1\""
486 ) >fake-editor-utf8-mime &&
487 chmod +x fake-editor-utf8-mime &&
408 - echo y | \
488 GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
410 - GIT_SEND_EMAIL_NOTTY=1 \
489 git send-email \
490 --compose --subject foo \
491 --from="Example <nobody@example.com>" \
@@ -421,9 +499,7 @@ test_expect_success '--compose respects user mime type' '
499
500 test_expect_success '--compose adds MIME for utf8 subject' '
501 clean_fake_sendmail &&
424 - echo y | \
502 GIT_EDITOR="\"$(pwd)/fake-editor\"" \
426 - GIT_SEND_EMAIL_NOTTY=1 \
503 git send-email \
504 --compose --subject utf8-sübjëct \
505 --from="Example <nobody@example.com>" \
@@ -445,7 +521,7 @@ test_expect_success 'detects ambiguous reference/file conflict' '
521 test_expect_success 'feed two files' '
522 rm -fr outdir &&
523 git format-patch -2 -o outdir &&
448 - GIT_SEND_EMAIL_NOTTY=1 git send-email \
524 + git send-email \
525 --dry-run \
526 --from="Example <nobody@example.com>" \
527 --to=nobody@example.com \