diff mbox series

[4/5] send-email: fix regression in sendemail.identity parsing

Message ID 20190517195545.29729-5-avarab@gmail.com (mailing list archive)
State New, archived
Headers show
Series ab/send-email-transferencoding-fix-for-the-fix | expand

Commit Message

Ævar Arnfjörð Bjarmason May 17, 2019, 7:55 p.m. UTC
Fix a regression in my recent 3494dfd3ee ("send-email: do defaults ->
config -> getopt in that order", 2019-05-09). I missed that the
$identity variable needs to be extracted from the command-line before
we do the config reading, as it determines which config variable we
should read first. See [1] for the report.

The sendemail.identity feature was added back in
34cc60ce2b ("send-email: Add support for SSL and SMTP-AUTH",
2007-09-03), there were no tests to assert that it worked properly.

So let's fix both the regression, and add some tests to assert that
this is being parsed properly. While I'm at it I'm adding a
--no-identity option to go with --[to|cc|bcc] variable, since the
semantics are similar. It's like to/cc/bcc except that unlike those we
don't support multiple identities, but we could now easily add it
support for it if anyone cares.

In just fixing the --identity command-line parsing bug I discovered
that a narrow fix to that wouldn't do. In read_config() we had a state
machine that would only set config values if they weren't set already,
and thus by proxy we wouldn't e.g. set "to" based on sendemail.to if
we'd seen sendemail.gmail.to before, with --identity=gmail.

I'd modified some of the relevant code in 3494dfd3ee, but just
reverting to that wouldn't do, since it would bring back the
regression fixed in that commit.

Refactor read_config() do what we actually mean here. We don't want to
set a given sendemail.VAR if a sendemail.$identity.VAR previously set
it. The old code was conflating this desire with the hardcoded
defaults for these variables, and as discussed in 3494dfd3ee that was
never going to work. Instead pass along the state of whether an
identity config set something before, as distinguished from the state
of the default just being false, or the default being a non-bool or
true (e.g. --transferencoding).

I'm still not happy with the test coverage here, e.g. there's nothing
testing sendemail.smtpEncryption, but I only have so much time to fix
this code.

1. https://public-inbox.org/git/5cddeb61.1c69fb81.47ed4.e648@mx.google.com/

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/git-send-email.txt |  4 ++
 git-send-email.perl              | 60 ++++++++++++++++++++----------
 t/t9001-send-email.sh            | 64 ++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 20 deletions(-)

Comments

Junio C Hamano May 19, 2019, 1:29 a.m. UTC | #1
Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Fix a regression in my recent 3494dfd3ee ("send-email: do defaults ->
> config -> getopt in that order", 2019-05-09). I missed that the
> $identity variable needs to be extracted from the command-line before
> we do the config reading, as it determines which config variable we
> should read first. See [1] for the report.
> ...
> Refactor read_config() do what we actually mean here. We don't want to
> set a given sendemail.VAR if a sendemail.$identity.VAR previously set
> it. The old code was conflating this desire with the hardcoded
> defaults for these variables, and as discussed in 3494dfd3ee that was
> never going to work.

I am not sure if the "never going to work" claim is a correct one.
The "no hardcoded default in the variable, read command line, fill
missing ones from the two config files and finally apply the default
for the ones that are still missing" was cumbersome, error-prone
without a table, but did work.

It seems that no matter how we cut it, the cumbersomeness has to
exist, as long as the command line --identity needs to be taken care
of.  Without that complication, I really liked the base series---the
"set var to hardcoded default, overwrite with config and then
overwrite with command line, without having to check if we already
got value in an earlier step" was so much simpler and easy to
explain X-<.

> @@ -371,17 +380,30 @@ sub read_config {
> ...
>  my $help;
>  my $git_completion_helper;
> -my $rc = GetOptions("h" => \$help,
> -                    "dump-aliases" => \$dump_aliases);
> ...
> @@ -410,8 +432,6 @@ sub read_config {
> ...
>  		    "smtp-auth=s" => \$smtp_auth,
> -		    "no-smtp-auth" => sub {$smtp_auth = 'none'},
> -		    "identity=s" => \$identity,

You seem to be building on top of ab/send-email-transferencoding-fix
and something else, and these two hunks did not apply.  I think I
managed to wiggle the patch in correctly, though.

Thanks.
Johannes Schindelin May 22, 2019, 8:25 p.m. UTC | #2
Hi Ævar,

On Fri, 17 May 2019, Ævar Arnfjörð Bjarmason wrote:

> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
> index 61d484d1a6..890e2874c3 100755
> --- a/t/t9001-send-email.sh
> +++ b/t/t9001-send-email.sh
> @@ -1228,6 +1228,61 @@ test_expect_success $PREREQ 'sendemail.to works' '
>  	grep "To: Somebody <somebody@ex.com>" stdout
>  '
>
> +test_expect_success $PREREQ 'setup sendemail.identity' '
> +	git config --replace-all sendemail.to "default@example.com" &&
> +	git config --replace-all sendemail.isp.to "isp@example.com" &&
> +	git config --replace-all sendemail.cloud.to "cloud@example.com"
> +'
> +
> +test_expect_success $PREREQ 'sendemail.identity: reads the correct identity config' '
> +	git -c sendemail.identity=cloud send-email \
> +		--dry-run \
> +		--from="nobody@example.com" \
> +		$patches >stdout &&
> +	grep "To: cloud@example.com" stdout
> +'
> +
> +test_expect_success $PREREQ 'sendemail.identity: identity overrides sendemail.identity' '
> +	git -c sendemail.identity=cloud send-email \
> +		--identity=isp \
> +		--dry-run \
> +		--from="nobody@example.com" \
> +		$patches >stdout &&
> +	grep "To: isp@example.com" stdout
> +'
> +
> +test_expect_success $PREREQ 'sendemail.identity: --no-identity clears previous identity' '
> +	git -c sendemail.identity=cloud send-email \
> +		--no-identity \
> +		--dry-run \
> +		--from="nobody@example.com" \
> +		$patches >stdout &&
> +	grep "To: default@example.com" stdout
> +'
> +
> +test_expect_success $PREREQ 'sendemail.identity: bool identity variable existance overrides' '
> +	git -c sendemail.identity=cloud \
> +		-c sendemail.xmailer=true \
> +		-c sendemail.cloud.xmailer=false \
> +		send-email \
> +		--dry-run \
> +		--from="nobody@example.com" \
> +		$patches >stdout &&
> +	grep "To: cloud@example.com" stdout &&
> +	! grep "X-Mailer" stdout
> +'
> +
> +test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '
> +	git -c sendemail.identity=cloud \
> +		-c sendemail.xmailer=false \
> +		send-email \
> +		--dry-run \
> +		--from="nobody@example.com" \
> +		$patches >stdout &&
> +	grep "To: cloud@example.com" stdout &&
> +	! grep "X-Mailer" stdout
> +'
> +

These test cases all diligently use the `$PREREQ` prerequisite, but...

>  test_expect_success $PREREQ '--no-to overrides sendemail.to' '
>  	git send-email \
>  		--dry-run \
> @@ -1785,6 +1840,15 @@ test_expect_success '--dump-aliases must be used alone' '
>  	test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
>  '
>
> +test_expect_success 'aliases and sendemail.identity' '
> +	test_must_fail git \
> +		-c sendemail.identity=cloud \
> +		-c sendemail.aliasesfile=default-aliases \
> +		-c sendemail.cloud.aliasesfile=cloud-aliases \
> +		send-email -1 2>stderr &&
> +	test_i18ngrep "cloud-aliases" stderr
> +'
> +

This one is missing it. That breaks the Windows job in our Azure Pipeline
where we leave out all of the Perl bits (to accelerate the tests somewhat).

Ciao,
Dscho

>  test_sendmail_aliases () {
>  	msg="$1" && shift &&
>  	expect="$@" &&
> --
> 2.21.0.1020.gf2820cf01a
>
>
>
Johannes Schindelin May 29, 2019, 9:10 a.m. UTC | #3
Hi Junio & Ævar,

On Wed, 22 May 2019, Johannes Schindelin wrote:

> On Fri, 17 May 2019, Ævar Arnfjörð Bjarmason wrote:
>
> > [...]
> > +test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '
> > +	git -c sendemail.identity=cloud \
> > +		-c sendemail.xmailer=false \
> > +		send-email \
> > +		--dry-run \
> > +		--from="nobody@example.com" \
> > +		$patches >stdout &&
> > +	grep "To: cloud@example.com" stdout &&
> > +	! grep "X-Mailer" stdout
> > +'
> > +
>
> These test cases all diligently use the `$PREREQ` prerequisite, but...
>
> >  test_expect_success $PREREQ '--no-to overrides sendemail.to' '
> >  	git send-email \
> >  		--dry-run \
> > @@ -1785,6 +1840,15 @@ test_expect_success '--dump-aliases must be used alone' '
> >  	test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
> >  '
> >
> > +test_expect_success 'aliases and sendemail.identity' '
> > +	test_must_fail git \
> > +		-c sendemail.identity=cloud \
> > +		-c sendemail.aliasesfile=default-aliases \
> > +		-c sendemail.cloud.aliasesfile=cloud-aliases \
> > +		send-email -1 2>stderr &&
> > +	test_i18ngrep "cloud-aliases" stderr
> > +'
> > +
>
> This one is missing it. That breaks the Windows job in our Azure Pipeline
> where we leave out all of the Perl bits (to accelerate the tests somewhat).

For the record, this is still breaking our Azure Pipeline build.

Junio, would you terribly mind applying this on top (or fetching it from
the `shears/pu` branch at https://github.com/git-for-windows/git)?

-- snipsnap --
From 4c946f956d894676f68f5b130ab414d8ea75e97d Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Wed, 29 May 2019 11:09:23 +0200
Subject: [PATCH] SQUASH???

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 t/t9001-send-email.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 7caab8160fcf..7c5ef114ac90 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1840,7 +1840,7 @@ test_expect_success '--dump-aliases must be used alone' '
 	test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
 '

-test_expect_success 'aliases and sendemail.identity' '
+test_expect_success $PREREQ 'aliases and sendemail.identity' '
 	test_must_fail git \
 		-c sendemail.identity=cloud \
 		-c sendemail.aliasesfile=default-aliases \
--
2.22.0.rc1.windows.1.19.g571a93d65ff3
diff mbox series

Patch

diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 8100ff4b0f..a861934c69 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -282,6 +282,10 @@  Automating
 	Clears any list of "To:", "Cc:", "Bcc:" addresses previously
 	set via config.
 
+--no-identity::
+	Clears the previously read value of `sendemail.identity` set
+	via config, if any.
+
 --to-cmd=<command>::
 	Specify a command to execute once per patch file which
 	should generate patch file specific "To:" entries.
diff --git a/git-send-email.perl b/git-send-email.perl
index b2c4a77671..80cbbfd2b8 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -182,7 +182,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);
+my ($no_cc, $no_to, $no_bcc, $no_identity);
 my (@config_to, @getopt_to);
 my (@config_cc, @getopt_cc);
 my (@config_bcc, @getopt_bcc);
@@ -325,44 +325,53 @@  sub signal_handler {
 
 # Read our sendemail.* config
 sub read_config {
-	my ($prefix) = @_;
+	my ($configured, $prefix) = @_;
 
 	foreach my $setting (keys %config_bool_settings) {
 		my $target = $config_bool_settings{$setting};
 		my $v = Git::config_bool(@repo, "$prefix.$setting");
-		$$target = $v if defined $v;
+		next unless defined $v;
+		next if $configured->{$setting}++;
+		$$target = $v;
 	}
 
 	foreach my $setting (keys %config_path_settings) {
 		my $target = $config_path_settings{$setting};
 		if (ref($target) eq "ARRAY") {
-			unless (@$target) {
-				my @values = Git::config_path(@repo, "$prefix.$setting");
-				@$target = @values if (@values && defined $values[0]);
-			}
+			my @values = Git::config_path(@repo, "$prefix.$setting");
+			next unless @values;
+			next if $configured->{$setting}++;
+			@$target = @values;
 		}
 		else {
 			my $v = Git::config_path(@repo, "$prefix.$setting");
-			$$target = $v if defined $v;
+			next unless defined $v;
+			next if $configured->{$setting}++;
+			$$target = $v;
 		}
 	}
 
 	foreach my $setting (keys %config_settings) {
 		my $target = $config_settings{$setting};
 		if (ref($target) eq "ARRAY") {
-			unless (@$target) {
-				my @values = Git::config(@repo, "$prefix.$setting");
-				@$target = @values if (@values && defined $values[0]);
-			}
+			my @values = Git::config(@repo, "$prefix.$setting");
+			next unless @values;
+			next if $configured->{$setting}++;
+			@$target = @values;
 		}
 		else {
 			my $v = Git::config(@repo, "$prefix.$setting");
-			$$target = $v if defined $v;
+			next unless defined $v;
+			next if $configured->{$setting}++;
+			$$target = $v;
 		}
 	}
 
 	if (!defined $smtp_encryption) {
-		my $enc = Git::config(@repo, "$prefix.smtpencryption");
+		my $setting = "$prefix.smtpencryption";
+		my $enc = Git::config(@repo, $setting);
+		return unless defined $enc;
+		return if $configured->{$setting}++;
 		if (defined $enc) {
 			$smtp_encryption = $enc;
 		} elsif (Git::config_bool(@repo, "$prefix.smtpssl")) {
@@ -371,17 +380,30 @@  sub read_config {
 	}
 }
 
+# sendemail.identity yields to --identity. We must parse this
+# special-case first before the rest of the config is read.
 $identity = Git::config(@repo, "sendemail.identity");
-read_config("sendemail.$identity") if defined $identity;
-read_config("sendemail");
+my $rc = GetOptions(
+	"identity=s" => \$identity,
+	"no-identity" => \$no_identity,
+);
+usage() unless $rc;
+undef $identity if $no_identity;
+
+# Now we know enough to read the config
+{
+    my %configured;
+    read_config(\%configured, "sendemail.$identity") if defined $identity;
+    read_config(\%configured, "sendemail");
+}
 
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
 my $help;
 my $git_completion_helper;
-my $rc = GetOptions("h" => \$help,
-                    "dump-aliases" => \$dump_aliases);
+$rc = GetOptions("h" => \$help,
+                 "dump-aliases" => \$dump_aliases);
 usage() unless $rc;
 die __("--dump-aliases incompatible with other options\n")
     if !$help and $dump_aliases and @ARGV;
@@ -410,8 +432,6 @@  sub read_config {
 		    "smtp-debug:i" => \$debug_net_smtp,
 		    "smtp-domain:s" => \$smtp_domain,
 		    "smtp-auth=s" => \$smtp_auth,
-		    "no-smtp-auth" => sub {$smtp_auth = 'none'},
-		    "identity=s" => \$identity,
 		    "annotate!" => \$annotate,
 		    "no-annotate" => sub {$annotate = 0},
 		    "compose" => \$compose,
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 61d484d1a6..890e2874c3 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1228,6 +1228,61 @@  test_expect_success $PREREQ 'sendemail.to works' '
 	grep "To: Somebody <somebody@ex.com>" stdout
 '
 
+test_expect_success $PREREQ 'setup sendemail.identity' '
+	git config --replace-all sendemail.to "default@example.com" &&
+	git config --replace-all sendemail.isp.to "isp@example.com" &&
+	git config --replace-all sendemail.cloud.to "cloud@example.com"
+'
+
+test_expect_success $PREREQ 'sendemail.identity: reads the correct identity config' '
+	git -c sendemail.identity=cloud send-email \
+		--dry-run \
+		--from="nobody@example.com" \
+		$patches >stdout &&
+	grep "To: cloud@example.com" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.identity: identity overrides sendemail.identity' '
+	git -c sendemail.identity=cloud send-email \
+		--identity=isp \
+		--dry-run \
+		--from="nobody@example.com" \
+		$patches >stdout &&
+	grep "To: isp@example.com" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.identity: --no-identity clears previous identity' '
+	git -c sendemail.identity=cloud send-email \
+		--no-identity \
+		--dry-run \
+		--from="nobody@example.com" \
+		$patches >stdout &&
+	grep "To: default@example.com" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.identity: bool identity variable existance overrides' '
+	git -c sendemail.identity=cloud \
+		-c sendemail.xmailer=true \
+		-c sendemail.cloud.xmailer=false \
+		send-email \
+		--dry-run \
+		--from="nobody@example.com" \
+		$patches >stdout &&
+	grep "To: cloud@example.com" stdout &&
+	! grep "X-Mailer" stdout
+'
+
+test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '
+	git -c sendemail.identity=cloud \
+		-c sendemail.xmailer=false \
+		send-email \
+		--dry-run \
+		--from="nobody@example.com" \
+		$patches >stdout &&
+	grep "To: cloud@example.com" stdout &&
+	! grep "X-Mailer" stdout
+'
+
 test_expect_success $PREREQ '--no-to overrides sendemail.to' '
 	git send-email \
 		--dry-run \
@@ -1785,6 +1840,15 @@  test_expect_success '--dump-aliases must be used alone' '
 	test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting
 '
 
+test_expect_success 'aliases and sendemail.identity' '
+	test_must_fail git \
+		-c sendemail.identity=cloud \
+		-c sendemail.aliasesfile=default-aliases \
+		-c sendemail.cloud.aliasesfile=cloud-aliases \
+		send-email -1 2>stderr &&
+	test_i18ngrep "cloud-aliases" stderr
+'
+
 test_sendmail_aliases () {
 	msg="$1" && shift &&
 	expect="$@" &&