diff mbox series

[v4] send-email: prompt-dependent exit codes

Message ID 20230809171531.2564739-1-oswald.buddenhagen@gmx.de (mailing list archive)
State Superseded
Headers show
Series [v4] send-email: prompt-dependent exit codes | expand

Commit Message

Oswald Buddenhagen Aug. 9, 2023, 5:15 p.m. UTC
From the perspective of a scripted caller, failure to send (some) mails
is an error even if it was interactively requested, so it should be
indicated by a non-zero exit code.

This is technically a backwards-incompatible behavior change, and
therefore would be safer to make opt-in. However, the damage resulting
from reporting the situation too eagerly is expected to be trivial,
while the damage resulting from callers failing to opt-in (which is
obviously the status quo, and is likely to persist in most cases) is
potentially at least somewhat serious.

For interactive calls from the command line, interactive cancellation is
arguably not really an error, but a human user will be able to interpret
the somewhat unusual exit code in light of their immediately preceding
interactions just fine.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>

---
v4:
- add tests (which also cover the partial confirmation behavior in the
  first place)
- add docu
- rework commit message again
v3:
- use a tally instead of flags after all, as my seemingly simple idea
  apparently requires lots of thinking to grasp fully
- correct exit code when zero messages are to be sent. this cannot
  actually happen, as it induces an exit via usage() earlier on.
- unfold nested ternary to save junio's sanity (who proved his point by
  unfolding it slightly incorrectly)
- expand commit message
v2:
- fix do_quit() not resetting $sent_all

Cc: Junio C Hamano <gitster@pobox.com>
Cc: Phillip Wood <phillip.wood123@gmail.com>
---
 Documentation/git-send-email.txt |  9 ++++++++
 git-send-email.perl              | 32 ++++++++++++++++++++++++----
 t/t9001-send-email.sh            | 36 ++++++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 4 deletions(-)

Comments

Junio C Hamano Aug. 9, 2023, 7:15 p.m. UTC | #1
Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:

> From the perspective of a scripted caller, failure to send (some) mails
> is an error even if it was interactively requested, so it should be
> indicated by a non-zero exit code.

I would agree that there should be a way to ask the command to
indicate if some messages were not sent due to end-user request, but
I have to say "From the perspective of a scripted caller" is a gross
over generalization that I would not want to see in a commit log
message of the project I run.  Not all our scripters do not write
the scripts the way you do.

It should not be hard to make this opt-in, and I still think it
should be opt-in.

Thanks.
Oswald Buddenhagen Aug. 10, 2023, 10 a.m. UTC | #2
On Wed, Aug 09, 2023 at 12:15:42PM -0700, Junio C Hamano wrote:
>Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:
>
>> From the perspective of a scripted caller, failure to send (some) mails
>> is an error even if it was interactively requested, so it should be
>> indicated by a non-zero exit code.
>
>I would agree that there should be a way to ask the command to
>indicate if some messages were not sent due to end-user request, but
>I have to say "From the perspective of a scripted caller" is a gross
>over generalization that I would not want to see in a commit log
>message of the project I run.
>
this gross error can be fixed by adding "likely" to the sentence.

>It should not be hard to make this opt-in,
>
that doesn't matter.

>and I still think it should be opt-in.
>
and i still think this would significantly reduce the value of the 
change.

the very idea of having to explicitly request that the obviously right 
thing is done is intuitively silly, and shouldn't be seriously 
entertained unless changing existing behavior can plausibly lead to 
serious adverse consequences. the minor nuisance of having to adjust 
wrappers to explicitly accept the most likely unexpected case does not 
qualify as such.

fwiw, other users who noticed this problem most probably preempted it by 
adding appropriate --confirm=* and --suppress-* options. but this 
doesn't fit my use case of a "light" wrapper.

regards
Junio C Hamano Aug. 10, 2023, 7:56 p.m. UTC | #3
Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:

> fwiw, other users who noticed this problem most probably preempted it
> by adding appropriate --confirm=* and --suppress-* options. but this
> doesn't fit my use case of a "light" wrapper.

I have no problem with giving the command an option to allow it to
report if one or more messages were held back via its exit status.

Others may even want to know which ones among many messages weren't
sent, not just "there were some that weren't sent", to make it
easier to identify which ones to be resent after touching them up.

In any case, I consider such changes (including the "were there any
that weren't sent" bit) optional new features.

Thanks.
Oswald Buddenhagen Aug. 11, 2023, 12:11 p.m. UTC | #4
On Thu, Aug 10, 2023 at 12:56:31PM -0700, Junio C Hamano wrote:
>I have no problem with giving the command an option to allow it to
>report if one or more messages were held back via its exit status.
>
that doesn't give credit to the observation that there is an opportunity 
cost to not doing it automatically. it is much easier to imagine that a 
scripting user simply didn't consider the possibility of an interactive 
cancellation than that they really meant to ignore it. therefore, the 
status quo is very likely more harmful than the fallout of starting to 
more aggressively report the situation.

>Others may even want to know which ones among many messages weren't
>sent, not just "there were some that weren't sent", to make it
>easier to identify which ones to be resent after touching them up.
>
yeah, i've considered that as well.
i think that on the exit status side my solution is adequate - it 
reports in a sufficiently distinctive way that something didn't go quite 
as probably expected.

going further, i'm considering producing a mapping from sha1s to 
message-ids (which i'd persist for automatic chaining of re-rolls). this 
would provide the fine-grained reporting as a side effect.

regards
diff mbox series

Patch

diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 492a82323d..d6ce3a0bd9 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -480,6 +480,15 @@  include::includes/cmd-config-section-all.txt[]
 
 include::config/sendemail.txt[]
 
+
+EXIT STATUS
+-----------
+
+Zero is returned when all specified patches were sent, while one is returned
+when an error occurs. 10 is returned if the user interactively skips sending
+some patches, and 11 if they skip all patches.
+
+
 EXAMPLES
 --------
 Use gmail as the smtp server
diff --git a/git-send-email.perl b/git-send-email.perl
index affbb88509..cd4db84b7f 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -256,6 +256,26 @@  sub system_or_die {
 	die $msg if $msg;
 }
 
+my $sent_files = 0;
+
+sub do_exit {
+	if ($sent_files == @files) {
+		# All specified messages were sent
+		exit(0);
+	} elsif ($sent_files) {
+		# At least some messages were sent
+		exit(10);
+	} else {
+		# User cancelled on first message already
+		exit(11);
+	}
+}
+
+sub do_quit {
+	cleanup_compose_files();
+	do_exit();
+}
+
 sub do_edit {
 	if (!defined($editor)) {
 		$editor = Git::command_oneline('var', 'GIT_EDITOR');
@@ -1195,8 +1215,7 @@  sub validate_address {
 		if (/^d/i) {
 			return undef;
 		} elsif (/^q/i) {
-			cleanup_compose_files();
-			exit(0);
+			do_quit();
 		}
 		$address = ask("$to_whom ",
 			default => "",
@@ -1619,8 +1638,7 @@  sub send_message {
 		} elsif (/^e/i) {
 			return -1;
 		} elsif (/^q/i) {
-			cleanup_compose_files();
-			exit(0);
+			do_quit();
 		} elsif (/^a/i) {
 			$confirm = 'never';
 		}
@@ -2001,6 +2019,10 @@  sub process_file {
 		return 0;
 	}
 
+	if ($message_was_sent) {
+		$sent_files++;
+	}
+
 	# set up for the next message
 	if ($thread) {
 		if ($message_was_sent &&
@@ -2278,3 +2300,5 @@  sub body_or_subject_has_nonascii {
 	}
 	return 0;
 }
+
+do_exit();
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 48bf0af2ee..dce0b1c498 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1187,6 +1187,42 @@  test_expect_success $PREREQ 'confirm does not loop forever' '
 			$patches
 '
 
+test_confirm_many () {
+	clean_fake_sendmail
+	GIT_SEND_EMAIL_NOTTY=1 \
+	git send-email \
+		--confirm=auto \
+		--from="Example <nobody@example.com>" \
+		--to=nobody@example.com \
+		--smtp-server="$(pwd)/fake.sendmail" \
+		-2 <replies
+	echo $? >exit_sts
+	test_cmp expected_sts exit_sts || return 1
+	ls commandline* 2>/dev/null | wc -l > num_mails
+	test_cmp expected_num num_mails || return 1
+}
+
+test_expect_success $PREREQ 'interactively skip none' '
+	(echo y && echo y) > replies &&
+	echo 0 > expected_sts &&
+	echo 2 > expected_num &&
+	test_confirm_many
+'
+
+test_expect_success $PREREQ 'interactively skip some' '
+	(echo n && echo y) > replies &&
+	echo 10 > expected_sts &&
+	echo 1 > expected_num &&
+	test_confirm_many
+'
+
+test_expect_success $PREREQ 'interactively skip all' '
+	(echo n && echo n) > replies &&
+	echo 11 > expected_sts &&
+	echo 0 > expected_num &&
+	test_confirm_many
+'
+
 test_expect_success $PREREQ 'utf8 Cc is rfc2047 encoded' '
 	clean_fake_sendmail &&
 	rm -fr outdir &&