{"thread":{"id":"55673","subject":"[PATCH 0/9] send-email: various optimizations to speed up by >2x","startedAt":"2021-05-12T13:48:30Z","lastAt":"2021-07-10T23:23:11Z","messageCount":108,"participants":["Ævar Arnfjörð Bjarmason","Đoàn Trần Công Danh","Eric Wong","Eric Sunshine","Jeff King","Felipe Contreras","Junio C Hamano","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"424331","messageId":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":null,"subject":"[PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:16Z","receivedAt":"2021-05-12T13:48:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This is on top of my just-submitted [1] which in turn is on top of\nsend-email work of mine sitting in \"next\".\n\nI was meaning to hold off on these patches for a bit, but given the\nconcurrent on-list discussion about doing config discovery in\nsend-email I wanted to send this now.\n\nThis combines by not-picked-up[1] recent patches to remove the support\nfor the \"sendemail.smtpssl\" variable with the later patches showing\nwhere that effort was really going.\n\nAs noted in the subject this speeds up git-send-email invocations by\n~2x or more, and brings the very slow t9001 test from running in ~26s\non my box to ~12s. It's no longer consistently the slowest test I run.\n\nThis is basically done in two ways: We lazily invoke \"git config\" to\nget config, before it's very eager, and deferring Perl compilation\nwith s/use/require/g.\n\n1. https://lore.kernel.org/git/patch-1.1-92571a8cf7-20210512T094803Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/cover-0.2-00000000000-20210411T144128Z-avarab@gmail.com/\n\nÆvar Arnfjörð Bjarmason (9):\n  send-email: remove non-working support for \"sendemail.smtpssl\"\n  send-email: refactor sendemail.smtpencryption config parsing\n  send-email: lazily load config for a big speedup\n  send-email: lazily shell out to \"git var\"\n  send-email: use function syntax instead of barewords\n  send-email: get rid of indirect object syntax\n  send-email: lazily load modules for a big speedup\n  perl: lazily load some common Git.pm setup code\n  send-email: move trivial config handling to Perl\n\n Documentation/config/sendemail.txt |   3 -\n git-send-email.perl                | 145 +++++++++++++++++------------\n perl/Git.pm                        |  49 +++++-----\n 3 files changed, 111 insertions(+), 86 deletions(-)\n\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424332","messageId":"patch-1.9-85b706d43f-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 1/9] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:17Z","receivedAt":"2021-05-12T13:48:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the already dead code to support \"sendemail.smtssl\" by finally\nremoving the dead code supporting the configuration option.\n\nIn f6bebd121ac (git-send-email: add support for TLS via\nNet::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was\ndocumented as deprecated, later in 65180c66186 (List send-email config\noptions in config.txt., 2009-07-22) the \"sendemail.smtpssl\"\nconfiguration option was also documented as such.\n\nThen in in 3ff15040e22 (send-email: fix regression in\nsendemail.identity parsing, 2019-05-17) I unintentionally removed\nsupport for it by introducing a bug in read_config().\n\nAs can be seen from the diff context we've already returned unless\n$enc i defined, so it's not possible for us to reach the \"elsif\"\nbranch here. This code was therefore already dead since Git v2.23.0.\n\nSo let's just remove it. We were already 11 years into a stated\ndeprecation period of this variable when 3ff15040e22 landed, now it's\naround 13. Since it hasn't worked anyway for around 2 years it looks\nlike we can safely remove it.\n\nThe --smtp-ssl option is still deprecated, if someone cares they can\nfollow-up and remove that too, but unlike the config option that one\ncould still be in use in the wild. I'm just removing this code that's\nprovably unused already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/sendemail.txt | 3 ---\n git-send-email.perl                | 6 +-----\n 2 files changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt\nindex cbc5af42fd..50baa5d6bf 100644\n--- a/Documentation/config/sendemail.txt\n+++ b/Documentation/config/sendemail.txt\n@@ -8,9 +8,6 @@ sendemail.smtpEncryption::\n \tSee linkgit:git-send-email[1] for description.  Note that this\n \tsetting is not subject to the 'identity' mechanism.\n \n-sendemail.smtpssl (deprecated)::\n-\tDeprecated alias for 'sendemail.smtpEncryption = ssl'.\n-\n sendemail.smtpsslcertpath::\n \tPath to ca-certificates (either a directory or a single file).\n \tSet it to an empty string to disable certificate verification.\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 175da07d94..3ef194fe2d 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -383,11 +383,7 @@ sub read_config {\n \t\tmy $enc = Git::config(@repo, $setting);\n \t\treturn unless defined $enc;\n \t\treturn if $configured->{$setting}++;\n-\t\tif (defined $enc) {\n-\t\t\t$smtp_encryption = $enc;\n-\t\t} elsif (Git::config_bool(@repo, \"$prefix.smtpssl\")) {\n-\t\t\t$smtp_encryption = 'ssl';\n-\t\t}\n+\t\t$smtp_encryption = $enc;\n \t}\n }\n \n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424333","messageId":"patch-2.9-c22af817f1-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 2/9] send-email: refactor sendemail.smtpencryption config parsing","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:18Z","receivedAt":"2021-05-12T13:48:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"With the removal of the support for sendemail.smtpssl in the preceding\ncommit the parsing of sendemail.smtpencryption is no longer special,\nand can by moved to %config_settings.\n\nThis gets us rid of an unconditional call to Git::config(), which as\nwe'll see in subsequent commits matters for startup performance.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 9 +--------\n 1 file changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 3ef194fe2d..f9d859da3e 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -277,6 +277,7 @@ sub do_edit {\n );\n \n my %config_settings = (\n+    \"smtpencryption\" => \\$smtp_encryption,\n     \"smtpserver\" => \\$smtp_server,\n     \"smtpserverport\" => \\$smtp_server_port,\n     \"smtpserveroption\" => \\@smtp_server_options,\n@@ -377,14 +378,6 @@ sub read_config {\n \t\t\t$$target = $v;\n \t\t}\n \t}\n-\n-\tif (!defined $smtp_encryption) {\n-\t\tmy $setting = \"$prefix.smtpencryption\";\n-\t\tmy $enc = Git::config(@repo, $setting);\n-\t\treturn unless defined $enc;\n-\t\treturn if $configured->{$setting}++;\n-\t\t$smtp_encryption = $enc;\n-\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424334","messageId":"patch-3.9-1e14d32253-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 3/9] send-email: lazily load config for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:19Z","receivedAt":"2021-05-12T13:48:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Reduce the time it takes git-send-email to get to even the most\ntrivial of tasks (such as serving up its \"-h\" output) by first listing\nconfig keys that exist, and only then only call e.g. \"git config\n--bool\" on them if they do.\n\nOver a lot of runs this speeds the time to \"-h\" up for me from ~250ms\nto ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to\n~20s.\n\nThis introduces a race condition where we'll do the \"wrong\" thing if a\nconfig key were to be inserted between us discovering the list and\ncalling read_config(), i.e. we won't know about the racily added\nkey. In theory this is a change in behavior, in practice it doesn't\nmatter.\n\nThe config_regexp() function being changed here was added in\ndd84e528a34 (git-send-email: die if sendmail.* config is set,\n2020-07-23) for use by git-send-email. So we can change its odd return\nvalue in the case where no values are found by \"git config\". The\ndifference in the *.pm code would matter if it was invoked in scalar\ncontext, but now it no longer is.\n\nArguably this caching belongs in Git.pm itself, but in lieu of\nmodifying it for all its callers let's only do this for \"git\nsend-email\". The other big potential win would be \"git svn\", but\nunlike \"git send-email\" it doesn't check tens of config variables one\nat a time at startup (in my brief testing it doesn't check any).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 35 ++++++++++++++++++++++++++---------\n perl/Git.pm         |  4 ++--\n 2 files changed, 28 insertions(+), 11 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f9d859da3e..5b0b7c33ec 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -337,11 +337,13 @@ sub signal_handler {\n \n # Read our sendemail.* config\n sub read_config {\n-\tmy ($configured, $prefix) = @_;\n+\tmy ($known_keys, $configured, $prefix) = @_;\n \n \tforeach my $setting (keys %config_bool_settings) {\n \t\tmy $target = $config_bool_settings{$setting};\n-\t\tmy $v = Git::config_bool(@repo, \"$prefix.$setting\");\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n+\t\tmy $v = Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -349,8 +351,10 @@ sub read_config {\n \n \tforeach my $setting (keys %config_path_settings) {\n \t\tmy $target = $config_path_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config_path(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config_path(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n@@ -365,14 +369,16 @@ sub read_config {\n \n \tforeach my $setting (keys %config_settings) {\n \t\tmy $target = $config_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy $v = Git::config(@repo, $key);\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -380,9 +386,20 @@ sub read_config {\n \t}\n }\n \n+# Save ourselves a lot of work of shelling out to 'git config' (it\n+# parses 'bool' etc.) by only doing so for config keys that exist.\n+my %known_config_keys;\n+{\n+\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n+\t@known_config_keys{@known_config_keys} = ();\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n-$identity = Git::config(@repo, \"sendemail.identity\");\n+{\n+\tmy $key = \"sendemail.identity\";\n+\t$identity = Git::config(@repo, $key) if exists $known_config_keys{$key};\n+}\n my $rc = GetOptions(\n \t\"identity=s\" => \\$identity,\n \t\"no-identity\" => \\$no_identity,\n@@ -393,8 +410,8 @@ sub read_config {\n # Now we know enough to read the config\n {\n     my %configured;\n-    read_config(\\%configured, \"sendemail.$identity\") if defined $identity;\n-    read_config(\\%configured, \"sendemail\");\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail.$identity\") if defined $identity;\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail\");\n }\n \n # Begin by accumulating all the variables (defined above), that we will end up\n@@ -478,7 +495,7 @@ sub read_config {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 73ebbf80cc..06a10b175a 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -754,8 +754,8 @@ sub config_regexp {\n \t} catch Git::Error::Command with {\n \t\tmy $E = shift;\n \t\tif ($E->value() == 1) {\n-\t\t\tmy @matches = ();\n-\t\t\treturn @matches;\n+\t\t\t# Key(s) not found.\n+\t\t\treturn;\n \t\t} else {\n \t\t\tthrow $E;\n \t\t}\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424335","messageId":"patch-4.9-e1df469d5f-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 4/9] send-email: lazily shell out to \"git var\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:20Z","receivedAt":"2021-05-12T13:48:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize git-send-email by only shelling out to \"git var\" if we need\nto. This is easily done by re-inventing our own small version of\nperl's Memoize module.\n\nI suppose I could just use Memoize itself, but in a subsequent patch\nI'll be micro-optimizing send-email's use of dependencies. Using\nMemoize is a measly extra 5-10 milliseconds, but as we'll see that'll\nend up mattering for us in the end.\n\nThis brings the runtime of a plain \"send-email\" from around ~160-170ms\nto ~140m-150ms. The runtime of the tests is around the same, or around\n~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++-----\n 1 file changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5b0b7c33ec..9ff315f775 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -560,8 +560,18 @@ sub read_config {\n }\n \n my ($repoauthor, $repocommitter);\n-($repoauthor) = Git::ident_person(@repo, 'author');\n-($repocommitter) = Git::ident_person(@repo, 'committer');\n+{\n+\tmy %cache;\n+\tmy ($author, $committer);\n+\tmy $common = sub {\n+\t\tmy ($what) = @_;\n+\t\treturn $cache{$what} if exists $cache{$what};\n+\t\t($cache{$what}) = Git::ident_person(@repo, $what);\n+\t\treturn $cache{$what};\n+\t};\n+\t$repoauthor = sub { $common->('author') };\n+\t$repocommitter = sub { $common->('committer') };\n+}\n \n sub parse_address_line {\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n@@ -749,7 +759,7 @@ sub get_patch_subject {\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n \n-\tmy $tpl_sender = $sender || $repoauthor || $repocommitter || '';\n+\tmy $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';\n \tmy $tpl_subject = $initial_subject || '';\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n@@ -955,7 +965,7 @@ sub file_declares_8bit_cte {\n \t$sender =~ s/^\\s+|\\s+$//g;\n \t($sender) = expand_aliases($sender);\n } else {\n-\t$sender = $repoauthor || $repocommitter || '';\n+\t$sender = $repoauthor->() || $repocommitter->() || '';\n }\n \n # $sender could be an already sanitized address\n@@ -1104,7 +1114,7 @@ sub make_message_id {\n \t$uniq = \"$message_id_stamp-$message_id_serial\";\n \n \tmy $du_part;\n-\tfor ($sender, $repocommitter, $repoauthor) {\n+\tfor ($sender, $repocommitter->(), $repoauthor->()) {\n \t\t$du_part = extract_valid_address(sanitize_address($_));\n \t\tlast if (defined $du_part and $du_part ne '');\n \t}\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424336","messageId":"patch-5.9-8846d40fc0-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 5/9] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:21Z","receivedAt":"2021-05-12T13:48:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\ndoesn't have to guess that \"__\" is a function. This makes the code\nmore readable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 9ff315f775..da46925aa0 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -678,7 +678,7 @@ sub is_format_patch_arg {\n \t\tif (defined($format_patch)) {\n \t\t\treturn $format_patch;\n \t\t}\n-\t\tdie sprintf(__ <<EOF, $f, $f);\n+\t\tdie sprintf(__(<<EOF), $f, $f);\n File '%s' exists but it could also be the range of commits\n to produce patches for.  Please disambiguate by...\n \n@@ -764,7 +764,7 @@ sub get_patch_subject {\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n \n-\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __ <<EOT2), <<EOT3;\n+\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __(<<EOT2)), <<EOT3;\n From $tpl_sender # This line is ignored.\n EOT1\n Lines beginning in \"GIT:\" will be removed.\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424337","messageId":"patch-6.9-0dde0e14ef-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 6/9] send-email: get rid of indirect object syntax","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:22Z","receivedAt":"2021-05-12T13:48:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change indirect object syntax such as \"new X ARGS\" to\n\"X->new(ARGS)\". This allows perl to see what \"new\" is at compile-time\nwithout having loaded Term::ReadLine. This doesn't matter now, but\nwill in a subsequent commit when we start lazily loading it.\n\nLet's do the same for the adjacent \"FakeTerm\" package for consistency,\neven though we're not going to conditionally load it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex da46925aa0..f9c780ceed 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -194,11 +194,11 @@ sub format_2822_time {\n my @repo = $repo ? ($repo) : ();\n my $term = eval {\n \t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? new Term::ReadLine 'git-send-email', \\*STDIN, \\*STDOUT\n-\t\t: new Term::ReadLine 'git-send-email';\n+\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t: Term::ReadLine->new('git-send-email');\n };\n if ($@) {\n-\t$term = new FakeTerm \"$@: going non-interactive\";\n+\t$term = FakeTerm->new(\"$@: going non-interactive\");\n }\n \n # Behavior modification variables\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424338","messageId":"patch-7.9-55a0b07062-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 7/9] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:23Z","receivedAt":"2021-05-12T13:48:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the time git-send-email takes to do even the simplest of\nthings (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\nlazily loading the modules it requires.\n\nBefore this change Devel::TraceUse would report 99/97 used modules\nunder NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\nto run t9001-send-email.sh, down from ~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 70 +++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 31 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f9c780ceed..907dc1425f 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -19,20 +19,10 @@\n use 5.008;\n use strict;\n use warnings;\n-use POSIX qw/strftime/;\n-use Term::ReadLine;\n use Getopt::Long;\n-use Text::ParseWords;\n-use Term::ANSIColor;\n-use File::Temp qw/ tempdir tempfile /;\n-use File::Spec::Functions qw(catdir catfile);\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n use Git;\n use Git::I18N;\n-use Net::Domain ();\n-use Net::SMTP ();\n-use Git::LoadCPAN::Mail::Address;\n \n Getopt::Long::Configure qw/ pass_through /;\n \n@@ -166,7 +156,6 @@ sub format_2822_time {\n \t\t       );\n }\n \n-my $have_email_valid = eval { require Email::Valid; 1 };\n my $smtp;\n my $auth;\n my $num_sent = 0;\n@@ -192,14 +181,6 @@ sub format_2822_time {\n \n my $repo = eval { Git->repository() };\n my @repo = $repo ? ($repo) : ();\n-my $term = eval {\n-\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n-\t\t: Term::ReadLine->new('git-send-email');\n-};\n-if ($@) {\n-\t$term = FakeTerm->new(\"$@: going non-interactive\");\n-}\n \n # Behavior modification variables\n my ($quiet, $dry_run) = (0, 0);\n@@ -309,9 +290,10 @@ sub do_edit {\n \n # Handle Uncouth Termination\n sub signal_handler {\n+\trequire Term::ANSIColor;\n \n \t# Make text normal\n-\tprint color(\"reset\"), \"\\n\";\n+\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n \n \t# SMTP password masked\n \tsystem \"stty echo\";\n@@ -574,11 +556,13 @@ sub read_config {\n }\n \n sub parse_address_line {\n+\trequire Git::LoadCPAN::Mail::Address;\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n }\n \n sub split_addrs {\n-\treturn quotewords('\\s*,\\s*', 1, @_);\n+\trequire Text::ParseWords;\n+\treturn Text::ParseWords::quotewords('\\s*,\\s*', 1, @_);\n }\n \n my %aliases;\n@@ -627,10 +611,11 @@ sub parse_sendmail_aliases {\n \t\t\ts/\\\\\"/\"/g foreach @addr;\n \t\t\t$aliases{$alias} = \\@addr\n \t\t}}},\n-\tmailrc => sub { my $fh = shift; while (<$fh>) {\n+\tmailrc => sub {\tmy $fh = shift; while (<$fh>) {\n \t\tif (/^alias\\s+(\\S+)\\s+(.*?)\\s*$/) {\n+\t\t\trequire Text::ParseWords;\n \t\t\t# spaces delimit multiple addresses\n-\t\t\t$aliases{$1} = [ quotewords('\\s+', 0, $2) ];\n+\t\t\t$aliases{$1} = [ Text::ParseWords::quotewords('\\s+', 0, $2) ];\n \t\t}}},\n \tpine => sub { my $fh = shift; my $f='\\t[^\\t]*';\n \t        for (my $x = ''; defined($x); $x = $_) {\n@@ -702,7 +687,8 @@ sub is_format_patch_arg {\n \t\topendir my $dh, $f\n \t\t\tor die sprintf(__(\"Failed to opendir %s: %s\"), $f, $!);\n \n-\t\tpush @files, grep { -f $_ } map { catfile($f, $_) }\n+\t\trequire File::Spec;\n+\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n \t\t\t\tsort readdir $dh;\n \t\tclosedir $dh;\n \t} elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {\n@@ -715,7 +701,8 @@ sub is_format_patch_arg {\n if (@rev_list_opts) {\n \tdie __(\"Cannot run git format-patch from outside a repository\\n\")\n \t\tunless $repo;\n-\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n+\trequire File::Temp;\n+\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n }\n \n @files = handle_backup_files(@files);\n@@ -752,9 +739,10 @@ sub get_patch_subject {\n if ($compose) {\n \t# Note that this does not need to be secure, but we will make a small\n \t# effort to have it be unique\n+\trequire File::Temp;\n \t$compose_filename = ($repo ?\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n \topen my $c, \">\", $compose_filename\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n@@ -861,6 +849,19 @@ sub get_patch_subject {\n \tdo_edit(@files);\n }\n \n+sub term {\n+\tmy $term = eval {\n+\t\trequire Term::ReadLine;\n+\t\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n+\t\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t\t: Term::ReadLine->new('git-send-email');\n+\t};\n+\tif ($@) {\n+\t\t$term = FakeTerm->new(\"$@: going non-interactive\");\n+\t}\n+\treturn $term;\n+}\n+\n sub ask {\n \tmy ($prompt, %arg) = @_;\n \tmy $valid_re = $arg{valid_re};\n@@ -868,6 +869,7 @@ sub ask {\n \tmy $confirm_only = $arg{confirm_only};\n \tmy $resp;\n \tmy $i = 0;\n+\tmy $term = term();\n \treturn defined $default ? $default : undef\n \t\tunless defined $term->IN and defined fileno($term->IN) and\n \t\t       defined $term->OUT and defined fileno($term->OUT);\n@@ -1048,6 +1050,7 @@ sub extract_valid_address {\n \treturn $address if ($address =~ /^($local_part_regexp)$/);\n \n \t$address =~ s/^\\s*<(.*)>\\s*$/$1/;\n+\tmy $have_email_valid = eval { require Email::Valid; 1 };\n \tif ($have_email_valid) {\n \t\treturn scalar Email::Valid->address($address);\n \t}\n@@ -1107,7 +1110,8 @@ sub validate_address_list {\n sub make_message_id {\n \tmy $uniq;\n \tif (!defined $message_id_stamp) {\n-\t\t$message_id_stamp = strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n+\t\trequire POSIX;\n+\t\t$message_id_stamp = POSIX::strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n \t\t$message_id_serial = 0;\n \t}\n \t$message_id_serial++;\n@@ -1277,6 +1281,7 @@ sub valid_fqdn {\n sub maildomain_net {\n \tmy $maildomain;\n \n+\trequire Net::Domain;\n \tmy $domain = Net::Domain::domainname();\n \t$maildomain = $domain if valid_fqdn($domain);\n \n@@ -1287,6 +1292,7 @@ sub maildomain_mta {\n \tmy $maildomain;\n \n \tfor my $host (qw(mailhost localhost)) {\n+\t\trequire Net::SMTP;\n \t\tmy $smtp = Net::SMTP->new($host);\n \t\tif (defined $smtp) {\n \t\t\tmy $domain = $smtp->domain;\n@@ -1965,13 +1971,15 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n-\t\tmy $validate_hook = catfile($repo->hooks_path(),\n+\t\trequire File::Spec;\n+\t\tmy $validate_hook = File::Spec->catfile($repo->hooks_path(),\n \t\t\t\t\t    'sendemail-validate');\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n-\t\t\tmy $target = abs_path($fn);\n+\t\t\trequire Cwd;\n+\t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = cwd();\n+\t\t\tmy $cwd_save = Cwd::cwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424339","messageId":"patch-8.9-2312346f71-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 8/9] perl: lazily load some common Git.pm setup code","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:24Z","receivedAt":"2021-05-12T13:48:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Instead of unconditionally requiring modules such as File::Spec, let's\nonly load them when needed. This speeds up code that only needs a\nsubset of the features Git.pm provides.\n\nThis brings a plain invocation of \"git send-email\" down from 52/37\nloaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes\n~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh\ntest is down to ~13s from ~15s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n perl/Git.pm | 35 ++++++++++++++++++-----------------\n 1 file changed, 18 insertions(+), 17 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 06a10b175a..f18852fb09 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -11,9 +11,6 @@ package Git;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n \n-use File::Temp ();\n-use File::Spec ();\n-\n BEGIN {\n \n our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);\n@@ -103,12 +100,9 @@ =head1 DESCRIPTION\n =cut\n \n \n-use Carp qw(carp croak); # but croak is bad - throw instead\n+sub carp { require Carp; goto &Carp::carp }\n+sub croak { require Carp; goto &Carp::croak }\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n-use IPC::Open2 qw(open2);\n-use Fcntl qw(SEEK_SET SEEK_CUR);\n-use Time::Local qw(timegm);\n }\n \n \n@@ -191,13 +185,15 @@ sub repository {\n \t\t\t$dir = undef;\n \t\t};\n \n+\t\trequire Cwd;\n \t\tif ($dir) {\n+\t\t\trequire File::Spec;\n \t\t\tFile::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;\n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \n \t\t\t# If --git-dir went ok, this shouldn't die either.\n \t\t\tmy $prefix = $search->command_oneline('rev-parse', '--show-prefix');\n-\t\t\t$dir = abs_path($opts{Directory}) . '/';\n+\t\t\t$dir = Cwd::abs_path($opts{Directory}) . '/';\n \t\t\tif ($prefix) {\n \t\t\t\tif (substr($dir, -length($prefix)) ne $prefix) {\n \t\t\t\t\tthrow Error::Simple(\"rev-parse confused me - $dir does not have trailing $prefix\");\n@@ -223,7 +219,7 @@ sub repository {\n \t\t\t\tthrow Error::Simple(\"fatal: Not a git repository: $dir\");\n \t\t\t}\n \n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \t\t}\n \n \t\tdelete $opts{Directory};\n@@ -408,10 +404,12 @@ sub command_bidi_pipe {\n \tmy $cwd_save = undef;\n \tif ($self) {\n \t\tshift;\n-\t\t$cwd_save = cwd();\n+\t\trequire Cwd;\n+\t\t$cwd_save = Cwd::cwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n-\t$pid = open2($in, $out, 'git', @_);\n+\trequire IPC::Open2;\n+\t$pid = IPC::Open2::open2($in, $out, 'git', @_);\n \tchdir($cwd_save) if $cwd_save;\n \treturn ($pid, $in, $out, join(' ', @_));\n }\n@@ -538,7 +536,8 @@ sub get_tz_offset {\n \tmy $t = shift || time;\n \tmy @t = localtime($t);\n \t$t[5] += 1900;\n-\tmy $gm = timegm(@t);\n+\trequire Time::Local;\n+\tmy $gm = Time::Local::timegm(@t);\n \tmy $sign = qw( + + - )[ $gm <=> $t ];\n \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n }\n@@ -629,7 +628,8 @@ sub hooks_path {\n \tmy ($self) = @_;\n \n \tmy $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');\n-\tmy $abs = abs_path($dir);\n+\trequire Cwd;\n+\tmy $abs = Cwd::abs_path($dir);\n \treturn $abs;\n }\n \n@@ -1353,6 +1353,7 @@ sub _temp_cache {\n \t\tmy $n = $name;\n \t\t$n =~ s/\\W/_/g; # no strange chars\n \n+\t\trequire File::Temp;\n \t\t($$temp_fd, $fname) = File::Temp::tempfile(\n \t\t\t\"Git_${n}_XXXXXX\", UNLINK => 1, DIR => $tmpdir,\n \t\t\t) or throw Error::Simple(\"couldn't open new temp file\");\n@@ -1375,9 +1376,9 @@ sub temp_reset {\n \n \ttruncate $temp_fd, 0\n \t\tor throw Error::Simple(\"couldn't truncate file\");\n-\tsysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET())\n \t\tor throw Error::Simple(\"couldn't seek to beginning of file\");\n-\tsysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0\n \t\tor throw Error::Simple(\"expected file position to be reset\");\n }\n \n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424340","messageId":"patch-9.9-0d87c9a5a3-20210512T132955Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-12T13:48:25Z","receivedAt":"2021-05-12T13:48:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the startup time of git-send-email by using an amended\nconfig_regexp() function to retrieve the list of config keys and\nvalues we're interested in.\n\nFor boolean keys we can handle the [true|false] case ourselves, and\nthe \"--get\" case didn't need any parsing. Let's leave \"--path\" and\nother \"--bool\" cases to \"git config\". As noted in a preceding commit\nwe're free to change the config_regexp() function, it's only used by\n\"git send-email\".\n\nThis brings the runtime of \"git send-email\" from ~60-~70ms to a very\nsteady ~40ms on my test box. We no run just one \"git config\"\ninvocation on startup instead of 8, the exact number will differ based\non the local sendemail.* config. I happen to have 8 of those set.\n\nThis brings the runtime of t9001-send-email.sh from ~13s down to ~12s\nfor me. The change there is less impressive as many of those tests set\nvarious config values, and we're also getting to the point of\ndiminishing returns for optimizing \"git send-email\" itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 17 ++++++++++-------\n perl/Git.pm         | 10 +++++-----\n 2 files changed, 15 insertions(+), 12 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 907dc1425f..35ba19470d 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -325,7 +325,10 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t $known_keys->{$key}->[0] =~ /^(?:true|false)$/s)\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -354,14 +357,12 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n-\t\t\tnext unless defined $v;\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n \t\t}\n@@ -372,8 +373,10 @@ sub read_config {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = Git::config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex f18852fb09..a9020d0d01 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -739,18 +739,18 @@ sub config_int {\n =item config_regexp ( RE )\n \n Retrieve the list of configuration key names matching the regular\n-expression C<RE>. The return value is a list of strings matching\n-this regex.\n+expression C<RE>. The return value is an ARRAY of key-value pairs.\n \n =cut\n \n sub config_regexp {\n \tmy ($self, $regex) = _maybe_self(@_);\n \ttry {\n-\t\tmy @cmd = ('config', '--name-only', '--get-regexp', $regex);\n+\t\tmy @cmd = ('config', '--null', '--get-regexp', $regex);\n \t\tunshift @cmd, $self if $self;\n-\t\tmy @matches = command(@cmd);\n-\t\treturn @matches;\n+\t\tmy $data = command(@cmd);\n+\t\tmy (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n+\t\treturn @kv;\n \t} catch Git::Error::Command with {\n \t\tmy $E = shift;\n \t\tif ($E->value() == 1) {\n-- \n2.31.1.909.g789bb6d90e\n\n"},{"id":"424347","messageId":"YJv4ReEAgHibU5Ir@danh.dev","threadId":"55673","inReplyTo":"patch-1.9-85b706d43f-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 1/9] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2021-05-12T15:46:13Z","receivedAt":"2021-05-12T16:45:24Z","isPatch":true,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2021-05-12 15:48:17+0200, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> Remove the already dead code to support \"sendemail.smtssl\" by finally\n\ns/smtssl/smtpssl/\n \n\n-- \nDanh\n"},{"id":"424365","messageId":"20210512180431.GA22854@dcvr","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Eric Wong","fromEmail":"e@80x24.org","sentAt":"2021-05-12T18:04:31Z","receivedAt":"2021-05-12T20:35:11Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> As noted in the subject this speeds up git-send-email invocations by\n> ~2x or more, and brings the very slow t9001 test from running in ~26s\n> on my box to ~12s. It's no longer consistently the slowest test I run.\n> \n> This is basically done in two ways: We lazily invoke \"git config\" to\n> get config, before it's very eager, and deferring Perl compilation\n> with s/use/require/g.\n\nNice.  I've been doing similar things elsewhere and hoping to\nfind time to get around to git-svn at some point.\n\n> Ævar Arnfjörð Bjarmason (9):\n>   send-email: remove non-working support for \"sendemail.smtpssl\"\n>   send-email: refactor sendemail.smtpencryption config parsing\n>   send-email: lazily load config for a big speedup\n>   send-email: lazily shell out to \"git var\"\n>   send-email: use function syntax instead of barewords\n>   send-email: get rid of indirect object syntax\n>   send-email: lazily load modules for a big speedup\n>   perl: lazily load some common Git.pm setup code\n>   send-email: move trivial config handling to Perl\n\nI spotted some further optimizations for 7 and 8,\nbut otherwise consider this series:\n\nReviewed-by: Eric Wong <e@80x24.org>\n"},{"id":"424366","messageId":"20210512180517.GA11354@dcvr","threadId":"55673","inReplyTo":"patch-8.9-2312346f71-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 8/9] perl: lazily load some common Git.pm setup code","fromName":"Eric Wong","fromEmail":"e@80x24.org","sentAt":"2021-05-12T18:05:17Z","receivedAt":"2021-05-12T20:36:40Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> @@ -408,10 +404,12 @@ sub command_bidi_pipe {\n>  \tmy $cwd_save = undef;\n>  \tif ($self) {\n>  \t\tshift;\n> -\t\t$cwd_save = cwd();\n> +\t\trequire Cwd;\n> +\t\t$cwd_save = Cwd::cwd();\n>  \t\t_setup_git_cmd_env($self);\n\n(This also applies to 7/9)\n\nCwd::cwd() execs /bin/pwd (at least on Perl 5.28.1 in Debian 10x).\nThere should be a benefit from using Cwd::getcwd() here, instead.\n\n\tstrace -f -e execve perl -MCwd -E 'Cwd::cwd()\n\n...confirms the execve is happening.\n\ngetcwd() takes 25ms vs 27ms of cwd() for me using the\n\"schedutil\" CPU governor under Linux:\n\n\ttime perl -MCwd -E 'Cwd::getcwd()'\n\ttime perl -MCwd -E 'Cwd::cwd()'\n"},{"id":"424371","messageId":"CAPig+cSxFzg5F6xfLK0hnWg5tjQ7qbcpaQb=SR0sY2tFQVrbvA@mail.gmail.com","threadId":"55673","inReplyTo":"patch-9.9-0d87c9a5a3-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-05-12T19:52:13Z","receivedAt":"2021-05-12T20:37:15Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, May 12, 2021 at 9:48 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> Optimize the startup time of git-send-email by using an amended\n> config_regexp() function to retrieve the list of config keys and\n> values we're interested in.\n>\n> For boolean keys we can handle the [true|false] case ourselves, and\n> the \"--get\" case didn't need any parsing. Let's leave \"--path\" and\n> other \"--bool\" cases to \"git config\". As noted in a preceding commit\n> we're free to change the config_regexp() function, it's only used by\n> \"git send-email\".\n>\n> This brings the runtime of \"git send-email\" from ~60-~70ms to a very\n> steady ~40ms on my test box. We no run just one \"git config\"\n\ns/no/now/\n\n> invocation on startup instead of 8, the exact number will differ based\n> on the local sendemail.* config. I happen to have 8 of those set.\n>\n> This brings the runtime of t9001-send-email.sh from ~13s down to ~12s\n> for me. The change there is less impressive as many of those tests set\n> various config values, and we're also getting to the point of\n> diminishing returns for optimizing \"git send-email\" itself.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"},{"id":"424394","messageId":"YJxgC/PJ/JIcIOfq@coredump.intra.peff.net","threadId":"55673","inReplyTo":"patch-9.9-0d87c9a5a3-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-12T23:08:59Z","receivedAt":"2021-05-12T23:28:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 12, 2021 at 03:48:25PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> Optimize the startup time of git-send-email by using an amended\n> config_regexp() function to retrieve the list of config keys and\n> values we're interested in.\n> \n> For boolean keys we can handle the [true|false] case ourselves, and\n> the \"--get\" case didn't need any parsing. Let's leave \"--path\" and\n> other \"--bool\" cases to \"git config\". As noted in a preceding commit\n> we're free to change the config_regexp() function, it's only used by\n> \"git send-email\".\n\nI think both of these cases should be safe.\n\nIt is a bit unfortunate to have to go through these contortions, but\nthis is definitely the best we can do for now. I think in the long run\nit would be nice to have a \"--stdin\" mode for git-config, where we could\ndo something like:\n\n  git config --stdin <<\\EOF\n  key=foo.bar\n  type=bool\n  default=false\n\n  key=another.key\n  type=color\n  default=red\n  EOF\n\nBut that doesn't exist yet, and using it would probably involve\nrearranging send-email a bit (we would need an up-front list of all of\nthe keys we care about and their types). So I'm perfectly content with\nthis strategy in the meantime.\n\n> diff --git a/perl/Git.pm b/perl/Git.pm\n> index f18852fb09..a9020d0d01 100644\n> --- a/perl/Git.pm\n> +++ b/perl/Git.pm\n> @@ -739,18 +739,18 @@ sub config_int {\n>  =item config_regexp ( RE )\n>  \n>  Retrieve the list of configuration key names matching the regular\n> -expression C<RE>. The return value is a list of strings matching\n> -this regex.\n> +expression C<RE>. The return value is an ARRAY of key-value pairs.\n\nThe Git.pm interface is public-facing, isn't it? Are we breaking\nthird-party callers of config_regexp here?\n\n-Peff\n"},{"id":"424396","messageId":"YJxgqA+AajRh2Q0J@coredump.intra.peff.net","threadId":"55673","inReplyTo":"patch-5.9-8846d40fc0-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 5/9] send-email: use function syntax instead of barewords","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-12T23:11:36Z","receivedAt":"2021-05-12T23:28:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 12, 2021 at 03:48:21PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\n> doesn't have to guess that \"__\" is a function. This makes the code\n> more readable.\n\nI think this is a good change. And while you do qualify it as \"more\"\nreadable, I think the main issue with readability here:\n\n> diff --git a/git-send-email.perl b/git-send-email.perl\n> index 9ff315f775..da46925aa0 100755\n> --- a/git-send-email.perl\n> +++ b/git-send-email.perl\n> @@ -678,7 +678,7 @@ sub is_format_patch_arg {\n>  \t\tif (defined($format_patch)) {\n>  \t\t\treturn $format_patch;\n>  \t\t}\n> -\t\tdie sprintf(__ <<EOF, $f, $f);\n> +\t\tdie sprintf(__(<<EOF), $f, $f);\n>  File '%s' exists but it could also be the range of commits\n>  to produce patches for.  Please disambiguate by...\n\nis how far the \"EOF\" marker is from the actual here-doc, syntactically\n(plus the ugly indentation). I suspect this and other places might be\nnicer to assign from the here-doc into a well-named variable.\n\nBut I think it's fine to leave for now (and I worry a bit that it may\nturn into a rabbit hole, so we might be better to just leave it alone\nanyway).\n\n-Peff\n"},{"id":"424402","messageId":"YJxmHbHAn4x93xsr@coredump.intra.peff.net","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-12T23:34:53Z","receivedAt":"2021-05-12T23:53:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> This combines by not-picked-up[1] recent patches to remove the support\n> for the \"sendemail.smtpssl\" variable with the later patches showing\n> where that effort was really going.\n> \n> As noted in the subject this speeds up git-send-email invocations by\n> ~2x or more, and brings the very slow t9001 test from running in ~26s\n> on my box to ~12s. It's no longer consistently the slowest test I run.\n\nNice. I have observed that with a decent number of cores, the running\ntime of the entire test suite correlates strongly with the running time\nof t9001. :)\n\nHere are timings for individual tests run with \"prove --state=slow,save\".\n(This is on an 8-core machine using -j32, skipping cvs/svn/p4 tests,\nand using a tmpfs via --root). The timings were computed with:\n\n  perl -MYAML -e '\n    $_ = do { local $/; <> };\n    # prove puts this non-YAML cruft at the end\n    s/\\.\\.\\.$//s;\n\n    my $t = YAML::Load($_)->{tests};\n    print \"$_->[1] $_->[0]\\n\" for\n      sort { $b->[1] <=> $a->[1] }\n      map { [$_, $t->{$_}->{elapsed}] }\n      keys(%$t);\n  ' t/.prove | head\n\nBefore your patches, the whole sweet takes ~60-63s, and the top timings\n(from a 63s run) are:\n\n    63.2607979774475 t9001-send-email.sh\n    51.742644071579 t0027-auto-crlf.sh\n    37.7909920215607 t3070-wildmatch.sh\n    27.09605717659 t7610-mergetool.sh\n    24.7028169631958 t7112-reset-submodule.sh\n    24.5535898208618 t5572-pull-submodule.sh\n    23.8404550552368 t9500-gitweb-standalone-no-errors.sh\n    22.3544380664825 t7400-submodule-basic.sh\n    21.7017750740051 t5510-fetch.sh\n    21.4575610160828 t3305-notes-fanout.sh\n\nNow after, which takes ~54-59s (this is from a 54s run):\n\n  46.796669960022 t0027-auto-crlf.sh\n  32.5747599601746 t3070-wildmatch.sh\n  21.5069420337677 t7610-mergetool.sh\n  20.8392388820648 t1701-racy-split-index.sh\n  19.7403028011322 t5572-pull-submodule.sh\n  19.7386808395386 t9001-send-email.sh\n  19.4622302055359 t7112-reset-submodule.sh\n  18.9555768966675 t9500-gitweb-standalone-no-errors.sh\n  18.0672709941864 t7400-submodule-basic.sh\n  17.641391992569 t5510-fetch.sh\n\nI have some messy patches to split t9001 into two segments. They were\nwaiting to get polished, but perhaps I can just discard them now. :)\n\nSome side notes for those interested in timing the test suite:\n\n  - If I run t9001 standalone, it goes much faster, of course; the CPU\n    throttles down when we're running all the tests in parallel.\n\n  - Those are with \"-x --verbose-log\", which is nice for catching flaky\n    results. Dropping those seems to shave a few seconds off.\n\n  - A big chunk of time for t0027 and t3070 is spent running the sed-based\n    chain-linting for their huge tables of auto-generated tests (1400+\n    and 1800+ respectively). Dropping the sed linting for just those\n    tests knocks off about 30 CPU-seconds.\n\n-Peff\n"},{"id":"424403","messageId":"YJxmmyM9/3KBFPTg@coredump.intra.peff.net","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-12T23:36:59Z","receivedAt":"2021-05-12T23:53:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> As noted in the subject this speeds up git-send-email invocations by\n> ~2x or more, and brings the very slow t9001 test from running in ~26s\n> on my box to ~12s. It's no longer consistently the slowest test I run.\n> \n> This is basically done in two ways: We lazily invoke \"git config\" to\n> get config, before it's very eager, and deferring Perl compilation\n> with s/use/require/g.\n\nSplitting my reply, since the other one got deep into test-suite timing\ndetails.\n\nThe techniques here look overall pretty reasonable. I think the module\nlazy-loading makes the overall code a _little_ uglier, but IMHO the\nspeedup you're getting is worth it (I was surprised how much of the\nimprovement comes from that versus avoiding git-config subprocesses).\n\nMy only concern is changing the interface of Git::config_regexp() in the\nfinal patch. Do we need to have a config_regexp_with_values() to avoid\nbreaking third-party users of the module?\n\n-Peff\n"},{"id":"424431","messageId":"609ccf688a4e7_329320879@natae.notmuch","threadId":"55673","inReplyTo":"YJxgC/PJ/JIcIOfq@coredump.intra.peff.net","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-13T07:04:08Z","receivedAt":"2021-05-13T07:04:16Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Jeff King wrote:\n> It is a bit unfortunate to have to go through these contortions, but\n> this is definitely the best we can do for now. I think in the long run\n> it would be nice to have a \"--stdin\" mode for git-config, where we could\n> do something like:\n> \n>   git config --stdin <<\\EOF\n>   key=foo.bar\n>   type=bool\n>   default=false\n> \n>   key=another.key\n>   type=color\n>   default=red\n>   EOF\n\nWhy do we even have to specify the type? Shouldn't there be a registry\nof configurations (a schema), so that all users don't have to do this?\n\n-- \nFelipe Contreras\n"},{"id":"424434","messageId":"YJzUvZjzSYuKcsTr@coredump.intra.peff.net","threadId":"55673","inReplyTo":"609ccf688a4e7_329320879@natae.notmuch","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-13T07:26:53Z","receivedAt":"2021-05-13T07:26:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:\n\n> Jeff King wrote:\n> > It is a bit unfortunate to have to go through these contortions, but\n> > this is definitely the best we can do for now. I think in the long run\n> > it would be nice to have a \"--stdin\" mode for git-config, where we could\n> > do something like:\n> > \n> >   git config --stdin <<\\EOF\n> >   key=foo.bar\n> >   type=bool\n> >   default=false\n> > \n> >   key=another.key\n> >   type=color\n> >   default=red\n> >   EOF\n> \n> Why do we even have to specify the type? Shouldn't there be a registry\n> of configurations (a schema), so that all users don't have to do this?\n\nOne of the purposes of git-config is to serve third-party scripts that\nstore their own config keys that Git does not know about. So we can't\nknow the set of all possible types that will be asked about.\n\nObviously we could have git-config know internally about all of the keys\nother parts of Git would ask about. But generally we have pushed that\nknowledge out to the users of the keys, rather than any kind of central\nregistry.\n\n-Peff\n"},{"id":"424435","messageId":"87v97nrsx0.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"609ccf688a4e7_329320879@natae.notmuch","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-13T07:07:57Z","receivedAt":"2021-05-13T07:31:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, May 13 2021, Felipe Contreras wrote:\n\n> Jeff King wrote:\n>> It is a bit unfortunate to have to go through these contortions, but\n>> this is definitely the best we can do for now. I think in the long run\n>> it would be nice to have a \"--stdin\" mode for git-config, where we could\n>> do something like:\n>> \n>>   git config --stdin <<\\EOF\n>>   key=foo.bar\n>>   type=bool\n>>   default=false\n>> \n>>   key=another.key\n>>   type=color\n>>   default=red\n>>   EOF\n>\n> Why do we even have to specify the type? Shouldn't there be a registry\n> of configurations (a schema), so that all users don't have to do this?\n\nYes, we should be moving towards that. Currently we're not there, the\nclosest we've got is the generated config-list.h.\n\nIf we closed that loop properly you should be able to do:\n\n    git config --get --type=dwim clean.requireForce # or auto, or no --type\n\nOr whatever, instead of:\n\n    git config --get --type=bool clean.requireForce\n\nBut we're not there yet, there's also edge cases here and there that\nmake a plain exhaustive registry hard, and send-email is one of them.\n\nIn its case the value of sendemail.identity=something (if any)\ndetermines if/how e.g. sendemail.something.smtpEncryption is\ninterpreted.\n\nI think we can do it with just a list of config variables where the\nvariable is either a string or a regex (in this case,\n\"^sendemail(?:[^.]+\\.)?\\.smtpEncryption$\"), but I haven't tried. There\nmay be other tricky special-cases.\n"},{"id":"424436","messageId":"87sg2rrsg1.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"YJxmmyM9/3KBFPTg@coredump.intra.peff.net","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-13T07:37:36Z","receivedAt":"2021-05-13T07:43:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, May 12 2021, Jeff King wrote:\n\n> On Wed, May 12, 2021 at 03:48:16PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n>> As noted in the subject this speeds up git-send-email invocations by\n>> ~2x or more, and brings the very slow t9001 test from running in ~26s\n>> on my box to ~12s. It's no longer consistently the slowest test I run.\n>> \n>> This is basically done in two ways: We lazily invoke \"git config\" to\n>> get config, before it's very eager, and deferring Perl compilation\n>> with s/use/require/g.\n>\n> Splitting my reply, since the other one got deep into test-suite timing\n> details.\n>\n> The techniques here look overall pretty reasonable. I think the module\n> lazy-loading makes the overall code a _little_ uglier, but IMHO the\n> speedup you're getting is worth it (I was surprised how much of the\n> improvement comes from that versus avoiding git-config subprocesses).\n\nYeah, it's mostly uglier, but I think it's worth it. Some parts are\nbetter afterwards though, i.e. the SOME_RARE_BARE_WORD is now\nModule::It::Is::In::SOME_RARE_BARE_WORD, which makes it easier to\nunderstand where it's from.\n\n> My only concern is changing the interface of Git::config_regexp() in the\n> final patch. Do we need to have a config_regexp_with_values() to avoid\n> breaking third-party users of the module?\n\nAs noted in 3/9 I don't think we need to worry about it, it's recently\nintroduced (a few months) API in Git.pm for send-email itself. I think\nwe can just change it.\n\nIn general I think it's unfortunate that we have (at least in principle)\na \"public by default\" module like Git.pm that's mostly for our own use.\n\nThis series doesn't try to deal with that in general at all, I'm\nsomewhat of the opinion that we should just fork it at this\npoint. I.e. have a Git.pm we freeze in time, and a Git/Ours.pm that's\ngoing to be the private API.\n\nI stopped with these optimizations at the point of refactoring away\nError.pm, which is a large contributor to compilation time, but as long\nas it's a public API that can't be done without changing the public\nAPI. If all we needed to worry about was send-email, git-svn etc. just\nchanging it to Perl-native exceptions would be trivial.\n"},{"id":"424439","messageId":"YJzZ8aSbt4ycX4ZH@coredump.intra.peff.net","threadId":"55673","inReplyTo":"87sg2rrsg1.fsf@evledraar.gmail.com","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-13T07:49:05Z","receivedAt":"2021-05-13T07:49:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 13, 2021 at 09:37:36AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> > My only concern is changing the interface of Git::config_regexp() in the\n> > final patch. Do we need to have a config_regexp_with_values() to avoid\n> > breaking third-party users of the module?\n> \n> As noted in 3/9 I don't think we need to worry about it, it's recently\n> introduced (a few months) API in Git.pm for send-email itself. I think\n> we can just change it.\n\nAh, thanks for pointing that out. I _thought_ I had seen you mention it\nearlier, but when I went back to look I couldn't find it.\n\nI'm not entirely convinced, though. I agree it's probably not heavily\nused, but the existing interface was shipped in three releases already\n(v2.29 and up).\n\n> In general I think it's unfortunate that we have (at least in principle)\n> a \"public by default\" module like Git.pm that's mostly for our own use.\n\nI'd certainly agree with that sentiment. :)\n\n> This series doesn't try to deal with that in general at all, I'm\n> somewhat of the opinion that we should just fork it at this\n> point. I.e. have a Git.pm we freeze in time, and a Git/Ours.pm that's\n> going to be the private API.\n> \n> I stopped with these optimizations at the point of refactoring away\n> Error.pm, which is a large contributor to compilation time, but as long\n> as it's a public API that can't be done without changing the public\n> API. If all we needed to worry about was send-email, git-svn etc. just\n> changing it to Perl-native exceptions would be trivial.\n\nYeah, I don't have any real problem with that, as long as we don't break\nthird-party scripts that we've promised not to. I'd even be OK with\ndeprecating Git.pm and eventually phasing it out, if we think it's a\nmaintenance burden.\n\n-Peff\n"},{"id":"424443","messageId":"609ce01c4d5df_5a82087a@natae.notmuch","threadId":"55673","inReplyTo":"YJzUvZjzSYuKcsTr@coredump.intra.peff.net","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-13T08:15:24Z","receivedAt":"2021-05-13T08:15:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Jeff King wrote:\n> On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:\n> \n> > Jeff King wrote:\n> > > It is a bit unfortunate to have to go through these contortions, but\n> > > this is definitely the best we can do for now. I think in the long run\n> > > it would be nice to have a \"--stdin\" mode for git-config, where we could\n> > > do something like:\n> > > \n> > >   git config --stdin <<\\EOF\n> > >   key=foo.bar\n> > >   type=bool\n> > >   default=false\n> > > \n> > >   key=another.key\n> > >   type=color\n> > >   default=red\n> > >   EOF\n> > \n> > Why do we even have to specify the type? Shouldn't there be a registry\n> > of configurations (a schema), so that all users don't have to do this?\n> \n> One of the purposes of git-config is to serve third-party scripts that\n> store their own config keys that Git does not know about. So we can't\n> know the set of all possible types that will be asked about.\n\nYes, I know, I maintain several tools that have such configurations. For\nthose you would need to specify the type (or find some way to install\nthe schema so that git parses it).\n\nBut I'm talking about git.git configurations. If you don't specify the\ntype in --stdin it should fetch it from some database. That would be\nmuch more user-friendly.\n\n-- \nFelipe Contreras\n"},{"id":"424463","messageId":"87h7j6svkj.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"609ce01c4d5df_5a82087a@natae.notmuch","subject":"Re: [PATCH 9/9] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-13T11:45:06Z","receivedAt":"2021-05-13T11:49:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, May 13 2021, Felipe Contreras wrote:\n\n> Jeff King wrote:\n>> On Thu, May 13, 2021 at 02:04:08AM -0500, Felipe Contreras wrote:\n>> \n>> > Jeff King wrote:\n>> > > It is a bit unfortunate to have to go through these contortions, but\n>> > > this is definitely the best we can do for now. I think in the long run\n>> > > it would be nice to have a \"--stdin\" mode for git-config, where we could\n>> > > do something like:\n>> > > \n>> > >   git config --stdin <<\\EOF\n>> > >   key=foo.bar\n>> > >   type=bool\n>> > >   default=false\n>> > > \n>> > >   key=another.key\n>> > >   type=color\n>> > >   default=red\n>> > >   EOF\n>> > \n>> > Why do we even have to specify the type? Shouldn't there be a registry\n>> > of configurations (a schema), so that all users don't have to do this?\n>> \n>> One of the purposes of git-config is to serve third-party scripts that\n>> store their own config keys that Git does not know about. So we can't\n>> know the set of all possible types that will be asked about.\n>\n> Yes, I know, I maintain several tools that have such configurations. For\n> those you would need to specify the type (or find some way to install\n> the schema so that git parses it).\n>\n> But I'm talking about git.git configurations. If you don't specify the\n> type in --stdin it should fetch it from some database. That would be\n> much more user-friendly.\n\nFor what it's worth my idea of hacking a plumbing thingy for\ngit-send-email here before ultimately deciding that my simpler caching\napproach was easier and gave me 95% of the win, was to just teach it a\nmode where it spews out all config variables \\0-delimited with all\npossible interpretations of it. I.e.:\n\n    some.variable\t123\tbool\ttrue\tpath\t123\tstring\t123\t[...]\n\nIt's rather cheap to do the \"interpret this as --type=X for me\" on the\nC-level, so we might as well spew out all possible interpretations.\n\nThat means that any external tool would be guaranteed to only need one\n\"git config\" invocation to parse any of its config, i.e. in a case where\nvariable X decides if variable Y is a bool or path or whatever. They'd\nalready have all possible values.\n\nSomething like:\n\n\tgit config -l -z --type=bool,path\n\tgit config -l -z --type=ALL\n\n"},{"id":"425061","messageId":"patch-01.10-8474acae689-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 01/10] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:18:58Z","receivedAt":"2021-05-20T08:19:21Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the already dead code to support \"sendemail.smtpssl\" by finally\nremoving the dead code supporting the configuration option.\n\nIn f6bebd121ac (git-send-email: add support for TLS via\nNet::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was\ndocumented as deprecated, later in 65180c66186 (List send-email config\noptions in config.txt., 2009-07-22) the \"sendemail.smtpssl\"\nconfiguration option was also documented as such.\n\nThen in in 3ff15040e22 (send-email: fix regression in\nsendemail.identity parsing, 2019-05-17) I unintentionally removed\nsupport for it by introducing a bug in read_config().\n\nAs can be seen from the diff context we've already returned unless\n$enc i defined, so it's not possible for us to reach the \"elsif\"\nbranch here. This code was therefore already dead since Git v2.23.0.\n\nSo let's just remove it. We were already 11 years into a stated\ndeprecation period of this variable when 3ff15040e22 landed, now it's\naround 13. Since it hasn't worked anyway for around 2 years it looks\nlike we can safely remove it.\n\nThe --smtp-ssl option is still deprecated, if someone cares they can\nfollow-up and remove that too, but unlike the config option that one\ncould still be in use in the wild. I'm just removing this code that's\nprovably unused already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/sendemail.txt | 3 ---\n git-send-email.perl                | 6 +-----\n 2 files changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt\nindex cbc5af42fdf..50baa5d6bfb 100644\n--- a/Documentation/config/sendemail.txt\n+++ b/Documentation/config/sendemail.txt\n@@ -8,9 +8,6 @@ sendemail.smtpEncryption::\n \tSee linkgit:git-send-email[1] for description.  Note that this\n \tsetting is not subject to the 'identity' mechanism.\n \n-sendemail.smtpssl (deprecated)::\n-\tDeprecated alias for 'sendemail.smtpEncryption = ssl'.\n-\n sendemail.smtpsslcertpath::\n \tPath to ca-certificates (either a directory or a single file).\n \tSet it to an empty string to disable certificate verification.\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 175da07d946..3ef194fe2da 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -383,11 +383,7 @@ sub read_config {\n \t\tmy $enc = Git::config(@repo, $setting);\n \t\treturn unless defined $enc;\n \t\treturn if $configured->{$setting}++;\n-\t\tif (defined $enc) {\n-\t\t\t$smtp_encryption = $enc;\n-\t\t} elsif (Git::config_bool(@repo, \"$prefix.smtpssl\")) {\n-\t\t\t$smtp_encryption = 'ssl';\n-\t\t}\n+\t\t$smtp_encryption = $enc;\n \t}\n }\n \n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425062","messageId":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"[PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:18:57Z","receivedAt":"2021-05-20T08:19:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"A re-roll of [1], the work I based v1 on top of has landed in master,\nso this has been rebased on master.\n\nThe changes here are minor, just a typo fix / commit message\nclarification, moving \"require\" closer to where it's used, and finally\na new 10/10 patch to s/cwd/getcwd/g.\n\nAs noted in the commit message I don't think that'll make any\ndifference in practice. The \"time\" Eric posted was for loading Cwd.pm\nand then doing cwd() or getcwd(), but when we run it we've already\npaid the cost of loading Cwd.pm. But it was an easy change to make, so\nlet's make it anyway.\n\n1. https://lore.kernel.org/git/cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com/\n\nÆvar Arnfjörð Bjarmason (10):\n  send-email: remove non-working support for \"sendemail.smtpssl\"\n  send-email: refactor sendemail.smtpencryption config parsing\n  send-email: lazily load config for a big speedup\n  send-email: lazily shell out to \"git var\"\n  send-email: use function syntax instead of barewords\n  send-email: get rid of indirect object syntax\n  send-email: lazily load modules for a big speedup\n  perl: lazily load some common Git.pm setup code\n  send-email: move trivial config handling to Perl\n  perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n\n Documentation/config/sendemail.txt |   3 -\n git-send-email.perl                | 146 +++++++++++++++++------------\n perl/Git.pm                        |  49 +++++-----\n 3 files changed, 111 insertions(+), 87 deletions(-)\n\nRange-diff against v1:\n 1:  92571a8cf7f <  -:  ----------- Makefile: make PERL_DEFINES recursively expanded\n 2:  85b706d43fc !  1:  8474acae689 send-email: remove non-working support for \"sendemail.smtpssl\"\n    @@ Metadata\n      ## Commit message ##\n         send-email: remove non-working support for \"sendemail.smtpssl\"\n     \n    -    Remove the already dead code to support \"sendemail.smtssl\" by finally\n    +    Remove the already dead code to support \"sendemail.smtpssl\" by finally\n         removing the dead code supporting the configuration option.\n     \n         In f6bebd121ac (git-send-email: add support for TLS via\n 3:  c22af817f10 =  2:  b87f53adbed send-email: refactor sendemail.smtpencryption config parsing\n 4:  1e14d322535 =  3:  1b27a393ae3 send-email: lazily load config for a big speedup\n 5:  e1df469d5fe =  4:  acee22b77d2 send-email: lazily shell out to \"git var\"\n 6:  8846d40fc02 =  5:  f317cd1c01e send-email: use function syntax instead of barewords\n 7:  0dde0e14ef6 =  6:  fc27024f838 send-email: get rid of indirect object syntax\n 8:  55a0b07062f !  7:  f86f5453d7a send-email: lazily load modules for a big speedup\n    @@ git-send-email.perl: sub do_edit {\n      \n      # Handle Uncouth Termination\n      sub signal_handler {\n    -+\trequire Term::ANSIColor;\n    - \n    +-\n      \t# Make text normal\n     -\tprint color(\"reset\"), \"\\n\";\n    ++\trequire Term::ANSIColor;\n     +\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n      \n      \t# SMTP password masked\n 9:  2312346f71e =  8:  86641377c0d perl: lazily load some common Git.pm setup code\n10:  0d87c9a5a37 !  9:  895c9e29a96 send-email: move trivial config handling to Perl\n    @@ Commit message\n     \n         Optimize the startup time of git-send-email by using an amended\n         config_regexp() function to retrieve the list of config keys and\n    -    values we're interested in.\n    +    values we're interested in. See the earlier \"send-email: lazily load\n    +    config for a big speedup\" commit for why changing its interface is OK.\n     \n         For boolean keys we can handle the [true|false] case ourselves, and\n         the \"--get\" case didn't need any parsing. Let's leave \"--path\" and\n    @@ Commit message\n         \"git send-email\".\n     \n         This brings the runtime of \"git send-email\" from ~60-~70ms to a very\n    -    steady ~40ms on my test box. We no run just one \"git config\"\n    +    steady ~40ms on my test box. We now run just one \"git config\"\n         invocation on startup instead of 8, the exact number will differ based\n         on the local sendemail.* config. I happen to have 8 of those set.\n     \n -:  ----------- > 10:  97455f993d5 perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425063","messageId":"patch-02.10-b87f53adbed-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 02/10] send-email: refactor sendemail.smtpencryption config parsing","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:18:59Z","receivedAt":"2021-05-20T08:19:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"With the removal of the support for sendemail.smtpssl in the preceding\ncommit the parsing of sendemail.smtpencryption is no longer special,\nand can by moved to %config_settings.\n\nThis gets us rid of an unconditional call to Git::config(), which as\nwe'll see in subsequent commits matters for startup performance.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 9 +--------\n 1 file changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 3ef194fe2da..f9d859da3ed 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -277,6 +277,7 @@ sub do_edit {\n );\n \n my %config_settings = (\n+    \"smtpencryption\" => \\$smtp_encryption,\n     \"smtpserver\" => \\$smtp_server,\n     \"smtpserverport\" => \\$smtp_server_port,\n     \"smtpserveroption\" => \\@smtp_server_options,\n@@ -377,14 +378,6 @@ sub read_config {\n \t\t\t$$target = $v;\n \t\t}\n \t}\n-\n-\tif (!defined $smtp_encryption) {\n-\t\tmy $setting = \"$prefix.smtpencryption\";\n-\t\tmy $enc = Git::config(@repo, $setting);\n-\t\treturn unless defined $enc;\n-\t\treturn if $configured->{$setting}++;\n-\t\t$smtp_encryption = $enc;\n-\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425064","messageId":"patch-03.10-1b27a393ae3-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 03/10] send-email: lazily load config for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:00Z","receivedAt":"2021-05-20T08:19:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Reduce the time it takes git-send-email to get to even the most\ntrivial of tasks (such as serving up its \"-h\" output) by first listing\nconfig keys that exist, and only then only call e.g. \"git config\n--bool\" on them if they do.\n\nOver a lot of runs this speeds the time to \"-h\" up for me from ~250ms\nto ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to\n~20s.\n\nThis introduces a race condition where we'll do the \"wrong\" thing if a\nconfig key were to be inserted between us discovering the list and\ncalling read_config(), i.e. we won't know about the racily added\nkey. In theory this is a change in behavior, in practice it doesn't\nmatter.\n\nThe config_regexp() function being changed here was added in\ndd84e528a34 (git-send-email: die if sendmail.* config is set,\n2020-07-23) for use by git-send-email. So we can change its odd return\nvalue in the case where no values are found by \"git config\". The\ndifference in the *.pm code would matter if it was invoked in scalar\ncontext, but now it no longer is.\n\nArguably this caching belongs in Git.pm itself, but in lieu of\nmodifying it for all its callers let's only do this for \"git\nsend-email\". The other big potential win would be \"git svn\", but\nunlike \"git send-email\" it doesn't check tens of config variables one\nat a time at startup (in my brief testing it doesn't check any).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 35 ++++++++++++++++++++++++++---------\n perl/Git.pm         |  4 ++--\n 2 files changed, 28 insertions(+), 11 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f9d859da3ed..5b0b7c33ec6 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -337,11 +337,13 @@ sub signal_handler {\n \n # Read our sendemail.* config\n sub read_config {\n-\tmy ($configured, $prefix) = @_;\n+\tmy ($known_keys, $configured, $prefix) = @_;\n \n \tforeach my $setting (keys %config_bool_settings) {\n \t\tmy $target = $config_bool_settings{$setting};\n-\t\tmy $v = Git::config_bool(@repo, \"$prefix.$setting\");\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n+\t\tmy $v = Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -349,8 +351,10 @@ sub read_config {\n \n \tforeach my $setting (keys %config_path_settings) {\n \t\tmy $target = $config_path_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config_path(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config_path(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n@@ -365,14 +369,16 @@ sub read_config {\n \n \tforeach my $setting (keys %config_settings) {\n \t\tmy $target = $config_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy $v = Git::config(@repo, $key);\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -380,9 +386,20 @@ sub read_config {\n \t}\n }\n \n+# Save ourselves a lot of work of shelling out to 'git config' (it\n+# parses 'bool' etc.) by only doing so for config keys that exist.\n+my %known_config_keys;\n+{\n+\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n+\t@known_config_keys{@known_config_keys} = ();\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n-$identity = Git::config(@repo, \"sendemail.identity\");\n+{\n+\tmy $key = \"sendemail.identity\";\n+\t$identity = Git::config(@repo, $key) if exists $known_config_keys{$key};\n+}\n my $rc = GetOptions(\n \t\"identity=s\" => \\$identity,\n \t\"no-identity\" => \\$no_identity,\n@@ -393,8 +410,8 @@ sub read_config {\n # Now we know enough to read the config\n {\n     my %configured;\n-    read_config(\\%configured, \"sendemail.$identity\") if defined $identity;\n-    read_config(\\%configured, \"sendemail\");\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail.$identity\") if defined $identity;\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail\");\n }\n \n # Begin by accumulating all the variables (defined above), that we will end up\n@@ -478,7 +495,7 @@ sub read_config {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 73ebbf80cc6..06a10b175a7 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -754,8 +754,8 @@ sub config_regexp {\n \t} catch Git::Error::Command with {\n \t\tmy $E = shift;\n \t\tif ($E->value() == 1) {\n-\t\t\tmy @matches = ();\n-\t\t\treturn @matches;\n+\t\t\t# Key(s) not found.\n+\t\t\treturn;\n \t\t} else {\n \t\t\tthrow $E;\n \t\t}\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425065","messageId":"patch-04.10-acee22b77d2-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 04/10] send-email: lazily shell out to \"git var\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:01Z","receivedAt":"2021-05-20T08:19:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize git-send-email by only shelling out to \"git var\" if we need\nto. This is easily done by re-inventing our own small version of\nperl's Memoize module.\n\nI suppose I could just use Memoize itself, but in a subsequent patch\nI'll be micro-optimizing send-email's use of dependencies. Using\nMemoize is a measly extra 5-10 milliseconds, but as we'll see that'll\nend up mattering for us in the end.\n\nThis brings the runtime of a plain \"send-email\" from around ~160-170ms\nto ~140m-150ms. The runtime of the tests is around the same, or around\n~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++-----\n 1 file changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5b0b7c33ec6..9ff315f775c 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -560,8 +560,18 @@ sub read_config {\n }\n \n my ($repoauthor, $repocommitter);\n-($repoauthor) = Git::ident_person(@repo, 'author');\n-($repocommitter) = Git::ident_person(@repo, 'committer');\n+{\n+\tmy %cache;\n+\tmy ($author, $committer);\n+\tmy $common = sub {\n+\t\tmy ($what) = @_;\n+\t\treturn $cache{$what} if exists $cache{$what};\n+\t\t($cache{$what}) = Git::ident_person(@repo, $what);\n+\t\treturn $cache{$what};\n+\t};\n+\t$repoauthor = sub { $common->('author') };\n+\t$repocommitter = sub { $common->('committer') };\n+}\n \n sub parse_address_line {\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n@@ -749,7 +759,7 @@ sub get_patch_subject {\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n \n-\tmy $tpl_sender = $sender || $repoauthor || $repocommitter || '';\n+\tmy $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';\n \tmy $tpl_subject = $initial_subject || '';\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n@@ -955,7 +965,7 @@ sub file_declares_8bit_cte {\n \t$sender =~ s/^\\s+|\\s+$//g;\n \t($sender) = expand_aliases($sender);\n } else {\n-\t$sender = $repoauthor || $repocommitter || '';\n+\t$sender = $repoauthor->() || $repocommitter->() || '';\n }\n \n # $sender could be an already sanitized address\n@@ -1104,7 +1114,7 @@ sub make_message_id {\n \t$uniq = \"$message_id_stamp-$message_id_serial\";\n \n \tmy $du_part;\n-\tfor ($sender, $repocommitter, $repoauthor) {\n+\tfor ($sender, $repocommitter->(), $repoauthor->()) {\n \t\t$du_part = extract_valid_address(sanitize_address($_));\n \t\tlast if (defined $du_part and $du_part ne '');\n \t}\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425066","messageId":"patch-05.10-f317cd1c01e-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 05/10] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:02Z","receivedAt":"2021-05-20T08:19:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\ndoesn't have to guess that \"__\" is a function. This makes the code\nmore readable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 9ff315f775c..da46925aa05 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -678,7 +678,7 @@ sub is_format_patch_arg {\n \t\tif (defined($format_patch)) {\n \t\t\treturn $format_patch;\n \t\t}\n-\t\tdie sprintf(__ <<EOF, $f, $f);\n+\t\tdie sprintf(__(<<EOF), $f, $f);\n File '%s' exists but it could also be the range of commits\n to produce patches for.  Please disambiguate by...\n \n@@ -764,7 +764,7 @@ sub get_patch_subject {\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n \n-\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __ <<EOT2), <<EOT3;\n+\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __(<<EOT2)), <<EOT3;\n From $tpl_sender # This line is ignored.\n EOT1\n Lines beginning in \"GIT:\" will be removed.\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425067","messageId":"patch-06.10-fc27024f838-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 06/10] send-email: get rid of indirect object syntax","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:03Z","receivedAt":"2021-05-20T08:19:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change indirect object syntax such as \"new X ARGS\" to\n\"X->new(ARGS)\". This allows perl to see what \"new\" is at compile-time\nwithout having loaded Term::ReadLine. This doesn't matter now, but\nwill in a subsequent commit when we start lazily loading it.\n\nLet's do the same for the adjacent \"FakeTerm\" package for consistency,\neven though we're not going to conditionally load it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex da46925aa05..f9c780ceed6 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -194,11 +194,11 @@ sub format_2822_time {\n my @repo = $repo ? ($repo) : ();\n my $term = eval {\n \t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? new Term::ReadLine 'git-send-email', \\*STDIN, \\*STDOUT\n-\t\t: new Term::ReadLine 'git-send-email';\n+\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t: Term::ReadLine->new('git-send-email');\n };\n if ($@) {\n-\t$term = new FakeTerm \"$@: going non-interactive\";\n+\t$term = FakeTerm->new(\"$@: going non-interactive\");\n }\n \n # Behavior modification variables\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425068","messageId":"patch-08.10-86641377c0d-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 08/10] perl: lazily load some common Git.pm setup code","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:05Z","receivedAt":"2021-05-20T08:19:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Instead of unconditionally requiring modules such as File::Spec, let's\nonly load them when needed. This speeds up code that only needs a\nsubset of the features Git.pm provides.\n\nThis brings a plain invocation of \"git send-email\" down from 52/37\nloaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes\n~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh\ntest is down to ~13s from ~15s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n perl/Git.pm | 35 ++++++++++++++++++-----------------\n 1 file changed, 18 insertions(+), 17 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 06a10b175a7..f18852fb09c 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -11,9 +11,6 @@ package Git;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n \n-use File::Temp ();\n-use File::Spec ();\n-\n BEGIN {\n \n our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);\n@@ -103,12 +100,9 @@ =head1 DESCRIPTION\n =cut\n \n \n-use Carp qw(carp croak); # but croak is bad - throw instead\n+sub carp { require Carp; goto &Carp::carp }\n+sub croak { require Carp; goto &Carp::croak }\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n-use IPC::Open2 qw(open2);\n-use Fcntl qw(SEEK_SET SEEK_CUR);\n-use Time::Local qw(timegm);\n }\n \n \n@@ -191,13 +185,15 @@ sub repository {\n \t\t\t$dir = undef;\n \t\t};\n \n+\t\trequire Cwd;\n \t\tif ($dir) {\n+\t\t\trequire File::Spec;\n \t\t\tFile::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;\n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \n \t\t\t# If --git-dir went ok, this shouldn't die either.\n \t\t\tmy $prefix = $search->command_oneline('rev-parse', '--show-prefix');\n-\t\t\t$dir = abs_path($opts{Directory}) . '/';\n+\t\t\t$dir = Cwd::abs_path($opts{Directory}) . '/';\n \t\t\tif ($prefix) {\n \t\t\t\tif (substr($dir, -length($prefix)) ne $prefix) {\n \t\t\t\t\tthrow Error::Simple(\"rev-parse confused me - $dir does not have trailing $prefix\");\n@@ -223,7 +219,7 @@ sub repository {\n \t\t\t\tthrow Error::Simple(\"fatal: Not a git repository: $dir\");\n \t\t\t}\n \n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \t\t}\n \n \t\tdelete $opts{Directory};\n@@ -408,10 +404,12 @@ sub command_bidi_pipe {\n \tmy $cwd_save = undef;\n \tif ($self) {\n \t\tshift;\n-\t\t$cwd_save = cwd();\n+\t\trequire Cwd;\n+\t\t$cwd_save = Cwd::cwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n-\t$pid = open2($in, $out, 'git', @_);\n+\trequire IPC::Open2;\n+\t$pid = IPC::Open2::open2($in, $out, 'git', @_);\n \tchdir($cwd_save) if $cwd_save;\n \treturn ($pid, $in, $out, join(' ', @_));\n }\n@@ -538,7 +536,8 @@ sub get_tz_offset {\n \tmy $t = shift || time;\n \tmy @t = localtime($t);\n \t$t[5] += 1900;\n-\tmy $gm = timegm(@t);\n+\trequire Time::Local;\n+\tmy $gm = Time::Local::timegm(@t);\n \tmy $sign = qw( + + - )[ $gm <=> $t ];\n \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n }\n@@ -629,7 +628,8 @@ sub hooks_path {\n \tmy ($self) = @_;\n \n \tmy $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');\n-\tmy $abs = abs_path($dir);\n+\trequire Cwd;\n+\tmy $abs = Cwd::abs_path($dir);\n \treturn $abs;\n }\n \n@@ -1353,6 +1353,7 @@ sub _temp_cache {\n \t\tmy $n = $name;\n \t\t$n =~ s/\\W/_/g; # no strange chars\n \n+\t\trequire File::Temp;\n \t\t($$temp_fd, $fname) = File::Temp::tempfile(\n \t\t\t\"Git_${n}_XXXXXX\", UNLINK => 1, DIR => $tmpdir,\n \t\t\t) or throw Error::Simple(\"couldn't open new temp file\");\n@@ -1375,9 +1376,9 @@ sub temp_reset {\n \n \ttruncate $temp_fd, 0\n \t\tor throw Error::Simple(\"couldn't truncate file\");\n-\tsysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET())\n \t\tor throw Error::Simple(\"couldn't seek to beginning of file\");\n-\tsysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0\n \t\tor throw Error::Simple(\"expected file position to be reset\");\n }\n \n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425069","messageId":"patch-07.10-f86f5453d7a-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 07/10] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:04Z","receivedAt":"2021-05-20T08:19:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the time git-send-email takes to do even the simplest of\nthings (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\nlazily loading the modules it requires.\n\nBefore this change Devel::TraceUse would report 99/97 used modules\nunder NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\nto run t9001-send-email.sh, down from ~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 71 +++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 32 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f9c780ceed6..a521f37c341 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -19,20 +19,10 @@\n use 5.008;\n use strict;\n use warnings;\n-use POSIX qw/strftime/;\n-use Term::ReadLine;\n use Getopt::Long;\n-use Text::ParseWords;\n-use Term::ANSIColor;\n-use File::Temp qw/ tempdir tempfile /;\n-use File::Spec::Functions qw(catdir catfile);\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n use Git;\n use Git::I18N;\n-use Net::Domain ();\n-use Net::SMTP ();\n-use Git::LoadCPAN::Mail::Address;\n \n Getopt::Long::Configure qw/ pass_through /;\n \n@@ -166,7 +156,6 @@ sub format_2822_time {\n \t\t       );\n }\n \n-my $have_email_valid = eval { require Email::Valid; 1 };\n my $smtp;\n my $auth;\n my $num_sent = 0;\n@@ -192,14 +181,6 @@ sub format_2822_time {\n \n my $repo = eval { Git->repository() };\n my @repo = $repo ? ($repo) : ();\n-my $term = eval {\n-\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n-\t\t: Term::ReadLine->new('git-send-email');\n-};\n-if ($@) {\n-\t$term = FakeTerm->new(\"$@: going non-interactive\");\n-}\n \n # Behavior modification variables\n my ($quiet, $dry_run) = (0, 0);\n@@ -309,9 +290,9 @@ sub do_edit {\n \n # Handle Uncouth Termination\n sub signal_handler {\n-\n \t# Make text normal\n-\tprint color(\"reset\"), \"\\n\";\n+\trequire Term::ANSIColor;\n+\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n \n \t# SMTP password masked\n \tsystem \"stty echo\";\n@@ -574,11 +555,13 @@ sub read_config {\n }\n \n sub parse_address_line {\n+\trequire Git::LoadCPAN::Mail::Address;\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n }\n \n sub split_addrs {\n-\treturn quotewords('\\s*,\\s*', 1, @_);\n+\trequire Text::ParseWords;\n+\treturn Text::ParseWords::quotewords('\\s*,\\s*', 1, @_);\n }\n \n my %aliases;\n@@ -627,10 +610,11 @@ sub parse_sendmail_aliases {\n \t\t\ts/\\\\\"/\"/g foreach @addr;\n \t\t\t$aliases{$alias} = \\@addr\n \t\t}}},\n-\tmailrc => sub { my $fh = shift; while (<$fh>) {\n+\tmailrc => sub {\tmy $fh = shift; while (<$fh>) {\n \t\tif (/^alias\\s+(\\S+)\\s+(.*?)\\s*$/) {\n+\t\t\trequire Text::ParseWords;\n \t\t\t# spaces delimit multiple addresses\n-\t\t\t$aliases{$1} = [ quotewords('\\s+', 0, $2) ];\n+\t\t\t$aliases{$1} = [ Text::ParseWords::quotewords('\\s+', 0, $2) ];\n \t\t}}},\n \tpine => sub { my $fh = shift; my $f='\\t[^\\t]*';\n \t        for (my $x = ''; defined($x); $x = $_) {\n@@ -702,7 +686,8 @@ sub is_format_patch_arg {\n \t\topendir my $dh, $f\n \t\t\tor die sprintf(__(\"Failed to opendir %s: %s\"), $f, $!);\n \n-\t\tpush @files, grep { -f $_ } map { catfile($f, $_) }\n+\t\trequire File::Spec;\n+\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n \t\t\t\tsort readdir $dh;\n \t\tclosedir $dh;\n \t} elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {\n@@ -715,7 +700,8 @@ sub is_format_patch_arg {\n if (@rev_list_opts) {\n \tdie __(\"Cannot run git format-patch from outside a repository\\n\")\n \t\tunless $repo;\n-\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n+\trequire File::Temp;\n+\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n }\n \n @files = handle_backup_files(@files);\n@@ -752,9 +738,10 @@ sub get_patch_subject {\n if ($compose) {\n \t# Note that this does not need to be secure, but we will make a small\n \t# effort to have it be unique\n+\trequire File::Temp;\n \t$compose_filename = ($repo ?\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n \topen my $c, \">\", $compose_filename\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n@@ -861,6 +848,19 @@ sub get_patch_subject {\n \tdo_edit(@files);\n }\n \n+sub term {\n+\tmy $term = eval {\n+\t\trequire Term::ReadLine;\n+\t\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n+\t\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t\t: Term::ReadLine->new('git-send-email');\n+\t};\n+\tif ($@) {\n+\t\t$term = FakeTerm->new(\"$@: going non-interactive\");\n+\t}\n+\treturn $term;\n+}\n+\n sub ask {\n \tmy ($prompt, %arg) = @_;\n \tmy $valid_re = $arg{valid_re};\n@@ -868,6 +868,7 @@ sub ask {\n \tmy $confirm_only = $arg{confirm_only};\n \tmy $resp;\n \tmy $i = 0;\n+\tmy $term = term();\n \treturn defined $default ? $default : undef\n \t\tunless defined $term->IN and defined fileno($term->IN) and\n \t\t       defined $term->OUT and defined fileno($term->OUT);\n@@ -1048,6 +1049,7 @@ sub extract_valid_address {\n \treturn $address if ($address =~ /^($local_part_regexp)$/);\n \n \t$address =~ s/^\\s*<(.*)>\\s*$/$1/;\n+\tmy $have_email_valid = eval { require Email::Valid; 1 };\n \tif ($have_email_valid) {\n \t\treturn scalar Email::Valid->address($address);\n \t}\n@@ -1107,7 +1109,8 @@ sub validate_address_list {\n sub make_message_id {\n \tmy $uniq;\n \tif (!defined $message_id_stamp) {\n-\t\t$message_id_stamp = strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n+\t\trequire POSIX;\n+\t\t$message_id_stamp = POSIX::strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n \t\t$message_id_serial = 0;\n \t}\n \t$message_id_serial++;\n@@ -1277,6 +1280,7 @@ sub valid_fqdn {\n sub maildomain_net {\n \tmy $maildomain;\n \n+\trequire Net::Domain;\n \tmy $domain = Net::Domain::domainname();\n \t$maildomain = $domain if valid_fqdn($domain);\n \n@@ -1287,6 +1291,7 @@ sub maildomain_mta {\n \tmy $maildomain;\n \n \tfor my $host (qw(mailhost localhost)) {\n+\t\trequire Net::SMTP;\n \t\tmy $smtp = Net::SMTP->new($host);\n \t\tif (defined $smtp) {\n \t\t\tmy $domain = $smtp->domain;\n@@ -1965,13 +1970,15 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n-\t\tmy $validate_hook = catfile($repo->hooks_path(),\n+\t\trequire File::Spec;\n+\t\tmy $validate_hook = File::Spec->catfile($repo->hooks_path(),\n \t\t\t\t\t    'sendemail-validate');\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n-\t\t\tmy $target = abs_path($fn);\n+\t\t\trequire Cwd;\n+\t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = cwd();\n+\t\t\tmy $cwd_save = Cwd::cwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425070","messageId":"patch-10.10-97455f993d5-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 10/10] perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:07Z","receivedAt":"2021-05-20T08:19:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"It has been pointed out[1] that cwd() invokes \"pwd(1)\" while getcwd()\nis a Perl-native XS function. For what we're using these for we can\nuse getcwd().\n\nThe performance difference is miniscule, we're saving on the order of\na millisecond or so, see [2] below for the benchmark. I don't think\nthis matters in practice for optimizing git-send-email or perl\nexecution (unlike the patches leading up to this one).\n\nBut let's do it regardless of that, if only so we don't have to think\nabout this as a low-hanging fruit anymore.\n\n1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/\n2.\n    $ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system \"pwd >/dev/null\" }})'\n                (warning: too few iterations for a reliable count)\n                             Rate                  pwd                 cwd    getcwd\n    pwd                     982/s                   --                -48%     -100%\n    cwd                    1890/s                  92%                  --     -100%\n    getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064%        -\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n perl/Git.pm         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 28b9a20a7f2..ff3008bfa34 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -1981,7 +1981,7 @@ sub validate_patch {\n \t\t\trequire Cwd;\n \t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = Cwd::cwd();\n+\t\t\tmy $cwd_save = Cwd::getcwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex a9020d0d01f..358a5f5625e 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -405,7 +405,7 @@ sub command_bidi_pipe {\n \tif ($self) {\n \t\tshift;\n \t\trequire Cwd;\n-\t\t$cwd_save = Cwd::cwd();\n+\t\t$cwd_save = Cwd::getcwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n \trequire IPC::Open2;\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425071","messageId":"patch-09.10-895c9e29a96-20210520T081826Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v2 09/10] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-20T08:19:06Z","receivedAt":"2021-05-20T08:19:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the startup time of git-send-email by using an amended\nconfig_regexp() function to retrieve the list of config keys and\nvalues we're interested in. See the earlier \"send-email: lazily load\nconfig for a big speedup\" commit for why changing its interface is OK.\n\nFor boolean keys we can handle the [true|false] case ourselves, and\nthe \"--get\" case didn't need any parsing. Let's leave \"--path\" and\nother \"--bool\" cases to \"git config\". As noted in a preceding commit\nwe're free to change the config_regexp() function, it's only used by\n\"git send-email\".\n\nThis brings the runtime of \"git send-email\" from ~60-~70ms to a very\nsteady ~40ms on my test box. We now run just one \"git config\"\ninvocation on startup instead of 8, the exact number will differ based\non the local sendemail.* config. I happen to have 8 of those set.\n\nThis brings the runtime of t9001-send-email.sh from ~13s down to ~12s\nfor me. The change there is less impressive as many of those tests set\nvarious config values, and we're also getting to the point of\ndiminishing returns for optimizing \"git send-email\" itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 17 ++++++++++-------\n perl/Git.pm         | 10 +++++-----\n 2 files changed, 15 insertions(+), 12 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex a521f37c341..28b9a20a7f2 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -324,7 +324,10 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t $known_keys->{$key}->[0] =~ /^(?:true|false)$/s)\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -353,14 +356,12 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n-\t\t\tnext unless defined $v;\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n \t\t}\n@@ -371,8 +372,10 @@ sub read_config {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = Git::config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex f18852fb09c..a9020d0d01f 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -739,18 +739,18 @@ sub config_int {\n =item config_regexp ( RE )\n \n Retrieve the list of configuration key names matching the regular\n-expression C<RE>. The return value is a list of strings matching\n-this regex.\n+expression C<RE>. The return value is an ARRAY of key-value pairs.\n \n =cut\n \n sub config_regexp {\n \tmy ($self, $regex) = _maybe_self(@_);\n \ttry {\n-\t\tmy @cmd = ('config', '--name-only', '--get-regexp', $regex);\n+\t\tmy @cmd = ('config', '--null', '--get-regexp', $regex);\n \t\tunshift @cmd, $self if $self;\n-\t\tmy @matches = command(@cmd);\n-\t\treturn @matches;\n+\t\tmy $data = command(@cmd);\n+\t\tmy (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n+\t\treturn @kv;\n \t} catch Git::Error::Command with {\n \t\tmy $E = shift;\n \t\tif ($E->value() == 1) {\n-- \n2.32.0.rc0.405.g5d387561bb3\n\n"},{"id":"425072","messageId":"YKYdeom6SgAHqojm@coredump.intra.peff.net","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-20T08:27:38Z","receivedAt":"2021-05-20T08:27:43Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 20, 2021 at 10:18:57AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> A re-roll of [1], the work I based v1 on top of has landed in master,\n> so this has been rebased on master.\n> \n> The changes here are minor, just a typo fix / commit message\n> clarification, moving \"require\" closer to where it's used, and finally\n> a new 10/10 patch to s/cwd/getcwd/g.\n\nI like all of this, except for the change in the interface of\nGit::config_regexp(). You mention that it's new-ish, and probably not in\nwide use. And I agree that's probably true. But it feels like violating\na principle of not breaking APIs, and we should stick to that principle\nand not bend it for \"well, it's not that old an API\".\n\nI'd find it more compelling if it the existing interface was broken or\nhard to avoid changing. But couldn't we just add a new function with the\nextra info (config_regexp_with_values() or something)?\n\n-Peff\n"},{"id":"425171","messageId":"xmqqv97drmge.fsf@gitster.g","threadId":"55673","inReplyTo":"YKYdeom6SgAHqojm@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-20T23:53:53Z","receivedAt":"2021-05-20T23:53:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I like all of this, except for the change in the interface of\n> Git::config_regexp(). You mention that it's new-ish, and probably not in\n> wide use. And I agree that's probably true. But it feels like violating\n> a principle of not breaking APIs, and we should stick to that principle\n> and not bend it for \"well, it's not that old an API\".\n>\n> I'd find it more compelling if it the existing interface was broken or\n> hard to avoid changing. But couldn't we just add a new function with the\n> extra info (config_regexp_with_values() or something)?\n\nWe seem to use it in-tree only once, but we have no control over\nwhat out-of-tree users have been doing.\n\nI do like your proposed name for the fact that it has _with_values\nin it; the original gave us only a list of configuration variable\nnames, and now we are getting name-value tuples.\n\nBUT\n\nI am not sure if I like the new interface, and I do not know if the\nthe name \"config_with_values\" is a good match for what it does.\n\nNamely, when a function is described as:\n\n    =item config_regexp ( RE )\n\n    Retrieve the list of configuration key names matching the regular\n    expression C<RE>. The return value is an ARRAY of key-value pairs.\n\nI would expect it to return ([key1, value1], [key2, value2], ...),\nbut the implementation returns (key1, value1, key2, value2, ...),\ni.e. a flattened list, if I am not mistaken.\n\n    my @cmd = ('config', '--null', '--get-regexp', $regex);\n    unshift @cmd, $self if $self;\n    my $data = command(@cmd);\n    my (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n    return @kv;\n\nWe get NUL terminated records, so we first split the entire output\nat NULs (to get a list of key-value records).  Each key-value record\nhas the key followed by LF followed by value, so we split it at the\nfirst LF (i.e. a value with an embedded LF would behave correctly)\nto extract key and value out of it.  But the resulting list is a\nflat list with alternating key and value.\n\nThe side that works on the returned value of courese knows that it\nis getting a flattened list and acts accordingly:\n\n    my @kv = Git::config_regexp(\"^sende?mail[.]\");\n    while (my ($k, $v) = splice @kv, 0, 2) {\n        push @{$known_config_keys{$k}} => $v;\n    }\n\nPerhaps it may be more performant than a more obvious and\nstraight-forward \"list of tuples\", i.e.\n\n    return map { [split /\\n/, $_, 2] } split /\\0/, $data;\n\n    my @kv = Git::config_regexp(\"^sende?mail[.]\");\n    for my $tuple (@kv) {\n        push @{$known_config_keys{$tuple->[0]}}, $tuple->[1];\n    }\n\nbut somehow the flattened list felt unnatural at least to a naïve\nreader like me.\n"},{"id":"425182","messageId":"875yzcpo52.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"xmqqv97drmge.fsf@gitster.g","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-21T06:23:15Z","receivedAt":"2021-05-21T07:00:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, May 21 2021, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n>\n>> I like all of this, except for the change in the interface of\n>> Git::config_regexp(). You mention that it's new-ish, and probably not in\n>> wide use. And I agree that's probably true. But it feels like violating\n>> a principle of not breaking APIs, and we should stick to that principle\n>> and not bend it for \"well, it's not that old an API\".\n>>\n>> I'd find it more compelling if it the existing interface was broken or\n>> hard to avoid changing. But couldn't we just add a new function with the\n>> extra info (config_regexp_with_values() or something)?\n>\n> We seem to use it in-tree only once, but we have no control over\n> what out-of-tree users have been doing.\n\nI thought people might have bought my argument in v1 that we just\ndocument these as \"public\" as part of some copy/pasting in Git.pm, but\nsure, I'll re-roll and make this a new function or something...\n\n> I do like your proposed name for the fact that it has _with_values\n> in it; the original gave us only a list of configuration variable\n> names, and now we are getting name-value tuples.\n>\n> BUT\n>\n> I am not sure if I like the new interface, and I do not know if the\n> the name \"config_with_values\" is a good match for what it does.\n>\n> Namely, when a function is described as:\n>\n>     =item config_regexp ( RE )\n>\n>     Retrieve the list of configuration key names matching the regular\n>     expression C<RE>. The return value is an ARRAY of key-value pairs.\n>\n> I would expect it to return ([key1, value1], [key2, value2], ...),\n> but the implementation returns (key1, value1, key2, value2, ...),\n> i.e. a flattened list, if I am not mistaken.\n\n\n...\n\n>     my @cmd = ('config', '--null', '--get-regexp', $regex);\n>     unshift @cmd, $self if $self;\n>     my $data = command(@cmd);\n>     my (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n>     return @kv;\n>\n> We get NUL terminated records, so we first split the entire output\n> at NULs (to get a list of key-value records).  Each key-value record\n> has the key followed by LF followed by value, so we split it at the\n> first LF (i.e. a value with an embedded LF would behave correctly)\n> to extract key and value out of it.  But the resulting list is a\n> flat list with alternating key and value.\n>\n> The side that works on the returned value of courese knows that it\n> is getting a flattened list and acts accordingly:\n>\n>     my @kv = Git::config_regexp(\"^sende?mail[.]\");\n>     while (my ($k, $v) = splice @kv, 0, 2) {\n>         push @{$known_config_keys{$k}} => $v;\n>     }\n>\n> Perhaps it may be more performant than a more obvious and\n> straight-forward \"list of tuples\", i.e.\n>\n>     return map { [split /\\n/, $_, 2] } split /\\0/, $data;\n>\n>     my @kv = Git::config_regexp(\"^sende?mail[.]\");\n>     for my $tuple (@kv) {\n>         push @{$known_config_keys{$tuple->[0]}}, $tuple->[1];\n>     }\n>\n> but somehow the flattened list felt unnatural at least to a naïve\n> reader like me.\n\nThe \"performant\" really doesn't matter here, we're comparing shelling\nout to getting a small number of config keys back. So I wasn't trying to\noptimize this.\n\nReturning a flattened list is idiomatic in Perl, it means that a caller\ncan do any of:\n\n    # I only care about the last value for a key, or only about\n    # existence checks\n    my %hash = func();\n\nOr:\n\n    # I want all key-values to iterate over\n    my @kv = func();\n\nReturning touples like this makes that less convenient for both, who'll\nneed to do more work to unpack them.\n\nFor what it's worth In Perl \"return a list\" means \"flattened list\", the\nterm \"flattened list\" I think comes from other languages. You'd call\nwhat you're suggesting a list of arrays, or (if a top-level reference)\nan array of arrays, AoA for short, AoH for array (ref) of hashes etc.\n"},{"id":"425183","messageId":"xmqq7djssgxc.fsf@gitster.g","threadId":"55673","inReplyTo":"875yzcpo52.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-21T07:07:59Z","receivedAt":"2021-05-21T07:08:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> Returning a flattened list is idiomatic in Perl, it means that a caller\n> can do any of:\n>\n>     # I only care about the last value for a key, or only about\n>     # existence checks\n>     my %hash = func();\n>\n> Or:\n>\n>     # I want all key-values to iterate over\n>     my @kv = func();\n>\n> Returning touples like this makes that less convenient for both, who'll\n> need to do more work to unpack them.\n\nThanks---that was exactly what I was missing.  Following establshed\npatterns is good.\n\nSo what remains is just the compatibility issues, I think.\n\nThanks for clarification.\n"},{"id":"425187","messageId":"YKd5xSw1bZbQXaAe@coredump.intra.peff.net","threadId":"55673","inReplyTo":"875yzcpo52.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-21T09:13:41Z","receivedAt":"2021-05-21T09:13:44Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 21, 2021 at 08:23:15AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> The \"performant\" really doesn't matter here, we're comparing shelling\n> out to getting a small number of config keys back. So I wasn't trying to\n> optimize this.\n> \n> Returning a flattened list is idiomatic in Perl, it means that a caller\n> can do any of:\n> \n>     # I only care about the last value for a key, or only about\n>     # existence checks\n>     my %hash = func();\n> \n> Or:\n> \n>     # I want all key-values to iterate over\n>     my @kv = func();\n> \n> Returning touples like this makes that less convenient for both, who'll\n> need to do more work to unpack them.\n> \n> For what it's worth In Perl \"return a list\" means \"flattened list\", the\n> term \"flattened list\" I think comes from other languages. You'd call\n> what you're suggesting a list of arrays, or (if a top-level reference)\n> an array of arrays, AoA for short, AoH for array (ref) of hashes etc.\n\nYeah, I think that is reasonable. But it made me wonder how we handle\nvalue-less booleans, and I think there's indeed a bug.\n\nTry a config like this:\n\n  $ cat >foo <<\\EOF\n  [foo]\n  key-with-value = string\n  key-with-empty =\n  just-bool\n  another-key-with-value = another\n  EOF\n\nA regular config --list looks like:\n\n  $ git config --file=foo --list\n  foo.key-with-value=string\n  foo.key-with-empty=\n  foo.just-bool\n  foo.another-key-with-value=another\n\nNote how \"just-bool\" drops the \"=\" to distinguish it from the empty\nstring. With \"-z\", it looks like this:\n\n  $ git config --file=foo --list -z\n  foo.key-with-value\n  string^@foo.key-with-empty\n  ^@foo.just-bool^@foo.another-key-with-value\n  another^@\n\nThe NULs separate keys, but keys are separated from their values by a\nnewline. And again, just-bool omits the newline.\n\nYour parser splits on newline, so for that entry we'll get only one\nstring returned in the list (the key), rather than two (the key and\nvalue). In a flattened list, that becomes ambiguous. E.g., adapting your\nparser into a stand-alone script:\n\n  $ git config --file=foo --list -z |\n    perl -e '\n      local $/;\n      my $data = <STDIN>;\n      my (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n\n      while (@kv) {\n        my $k = shift @kv;\n\tprint \"key: $k\\n\";\n\tmy $v = shift @kv;\n\tprint \"  value: \", (defined $v ? $v : \"undef\"), \"\\n\";\n      }\n    '\n  key: foo.key-with-value\n    value: string\n  key: foo.key-with-empty\n    value: \n  key: foo.just-bool\n    value: foo.another-key-with-value\n  key: another\n    value: undef\n\nWe end up misinterpreting a key as a value, and vice versa.\n\nUsing a non-flattened structure would have prevented this (we'd sensibly\nget undef when trying to access the missing second element of the\narray). But I do agree the flattened structure is more perl-ish.\nProbably you'd want to insert an explicit \"undef\" into the list. The\nmost perl-ish I could come up with is:\n\n  my (@kv) = map { my ($k, $v) = split /\\n/, $_, 2;\n                   ($k, $v)\n                 } split /\\0/, $data;\n\nI notice that $known_keys then becomes a non-flat representation. You'd\neither want to turn that back into a zero-length array there, or store\nthe \"undef\" and handle it appropriately (it can be a synonym for \"true\",\nthough that is just an optimization at this point).\n\n-Peff\n"},{"id":"425188","messageId":"8735ugphge.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"YKd5xSw1bZbQXaAe@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-21T09:24:00Z","receivedAt":"2021-05-21T09:26:03Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, May 21 2021, Jeff King wrote:\n\n> On Fri, May 21, 2021 at 08:23:15AM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n>> The \"performant\" really doesn't matter here, we're comparing shelling\n>> out to getting a small number of config keys back. So I wasn't trying to\n>> optimize this.\n>> \n>> Returning a flattened list is idiomatic in Perl, it means that a caller\n>> can do any of:\n>> \n>>     # I only care about the last value for a key, or only about\n>>     # existence checks\n>>     my %hash = func();\n>> \n>> Or:\n>> \n>>     # I want all key-values to iterate over\n>>     my @kv = func();\n>> \n>> Returning touples like this makes that less convenient for both, who'll\n>> need to do more work to unpack them.\n>> \n>> For what it's worth In Perl \"return a list\" means \"flattened list\", the\n>> term \"flattened list\" I think comes from other languages. You'd call\n>> what you're suggesting a list of arrays, or (if a top-level reference)\n>> an array of arrays, AoA for short, AoH for array (ref) of hashes etc.\n>\n> Yeah, I think that is reasonable. But it made me wonder how we handle\n> value-less booleans, and I think there's indeed a bug.\n>\n> Try a config like this:\n>\n>   $ cat >foo <<\\EOF\n>   [foo]\n>   key-with-value = string\n>   key-with-empty =\n>   just-bool\n>   another-key-with-value = another\n>   EOF\n>\n> A regular config --list looks like:\n>\n>   $ git config --file=foo --list\n>   foo.key-with-value=string\n>   foo.key-with-empty=\n>   foo.just-bool\n>   foo.another-key-with-value=another\n>\n> Note how \"just-bool\" drops the \"=\" to distinguish it from the empty\n> string. With \"-z\", it looks like this:\n>\n>   $ git config --file=foo --list -z\n>   foo.key-with-value\n>   string^@foo.key-with-empty\n>   ^@foo.just-bool^@foo.another-key-with-value\n>   another^@\n>\n> The NULs separate keys, but keys are separated from their values by a\n> newline. And again, just-bool omits the newline.\n>\n> Your parser splits on newline, so for that entry we'll get only one\n> string returned in the list (the key), rather than two (the key and\n> value). In a flattened list, that becomes ambiguous. E.g., adapting your\n> parser into a stand-alone script:\n>\n>   $ git config --file=foo --list -z |\n>     perl -e '\n>       local $/;\n>       my $data = <STDIN>;\n>       my (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n>\n>       while (@kv) {\n>         my $k = shift @kv;\n> \tprint \"key: $k\\n\";\n> \tmy $v = shift @kv;\n> \tprint \"  value: \", (defined $v ? $v : \"undef\"), \"\\n\";\n>       }\n>     '\n>   key: foo.key-with-value\n>     value: string\n>   key: foo.key-with-empty\n>     value: \n>   key: foo.just-bool\n>     value: foo.another-key-with-value\n>   key: another\n>     value: undef\n>\n> We end up misinterpreting a key as a value, and vice versa.\n>\n> Using a non-flattened structure would have prevented this (we'd sensibly\n> get undef when trying to access the missing second element of the\n> array). But I do agree the flattened structure is more perl-ish.\n> Probably you'd want to insert an explicit \"undef\" into the list. The\n> most perl-ish I could come up with is:\n>\n>   my (@kv) = map { my ($k, $v) = split /\\n/, $_, 2;\n>                    ($k, $v)\n>                  } split /\\0/, $data;\n>\n> I notice that $known_keys then becomes a non-flat representation. You'd\n> either want to turn that back into a zero-length array there, or store\n> the \"undef\" and handle it appropriately (it can be a synonym for \"true\",\n> though that is just an optimization at this point).\n>\n> -Peff\n\nAh yes, that's indeed a bug. I'd forgetten about the empty value case.\n\nFor what it's worth you can slightly golf that as (split /\\n/, $_,\n2)[0,1], but I think in this case your version is better than that, it's\nmore obvious what we're trying to do in always returning the $v.\n"},{"id":"425191","messageId":"YKd/QkWJtmeUJE8T@coredump.intra.peff.net","threadId":"55673","inReplyTo":"8735ugphge.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-21T09:37:06Z","receivedAt":"2021-05-21T09:37:10Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 21, 2021 at 11:24:00AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> > Using a non-flattened structure would have prevented this (we'd sensibly\n> > get undef when trying to access the missing second element of the\n> > array). But I do agree the flattened structure is more perl-ish.\n> > Probably you'd want to insert an explicit \"undef\" into the list. The\n> > most perl-ish I could come up with is:\n> >\n> >   my (@kv) = map { my ($k, $v) = split /\\n/, $_, 2;\n> >                    ($k, $v)\n> >                  } split /\\0/, $data;\n> >\n> > I notice that $known_keys then becomes a non-flat representation. You'd\n> > either want to turn that back into a zero-length array there, or store\n> > the \"undef\" and handle it appropriately (it can be a synonym for \"true\",\n> > though that is just an optimization at this point).\n> >\n> > -Peff\n> \n> Ah yes, that's indeed a bug. I'd forgetten about the empty value case.\n> \n> For what it's worth you can slightly golf that as (split /\\n/, $_,\n> 2)[0,1], but I think in this case your version is better than that, it's\n> more obvious what we're trying to do in always returning the $v.\n\nHeh. Thanks, I almost invited you to golf it because I was curious if we\ncould continue to do it in one line. I see I didn't need to ask. :)\n\nYours is clever and I'm glad to be enlightened, but I agree the\ntwo-liner is probably more obvious (perhaps even a comment like\n\"bool-only values omit the newline and become undef\" is worth it).\n\n-Peff\n"},{"id":"425357","messageId":"patch-01.13-71f890dc60-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 01/13] send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:06Z","receivedAt":"2021-05-23T08:56:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add support for the \"GIT_TEST_PERL_FATAL_WARNINGS=true\" test mode to\n\"send-email\". This was added to e.g. git-svn in 5338ed2b26 (perl:\ncheck for perl warnings while running tests, 2020-10-21), but not\n\"send-email\". Let's rectify that.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 175da07d94..cffdfdacfb 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -18,7 +18,7 @@\n \n use 5.008;\n use strict;\n-use warnings;\n+use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n use POSIX qw/strftime/;\n use Term::ReadLine;\n use Getopt::Long;\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425358","messageId":"patch-02.13-707c2ca556-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 02/13] send-email tests: test for boolean variables without a value","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:07Z","receivedAt":"2021-05-23T08:56:27Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The Git.pm code does its own Perl-ifying of boolean variables, let's\nensure that empty values = true for boolean variables, as in the C\ncode.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 65b3035371..6bf79c816a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -1349,6 +1349,16 @@ test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '\n \t! grep \"X-Mailer\" stdout\n '\n \n+test_expect_success $PREREQ 'sendemail.identity: bool variable without a value' '\n+\tgit -c sendemail.xmailer \\\n+\t\tsend-email \\\n+\t\t--dry-run \\\n+\t\t--from=\"nobody@example.com\" \\\n+\t\t$patches >stdout &&\n+\tgrep \"To: default@example.com\" stdout &&\n+\tgrep \"X-Mailer\" stdout\n+'\n+\n test_expect_success $PREREQ '--no-to overrides sendemail.to' '\n \tgit send-email \\\n \t\t--dry-run \\\n@@ -2073,6 +2083,18 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=true' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer' '\n+\ttest_when_finished \"test_unconfig sendemail.xmailer\" &&\n+\tcat >>.git/config <<-\\EOF &&\n+\t[sendemail]\n+\t\txmailer\n+\tEOF\n+\ttest_config sendemail.xmailer true &&\n+\tdo_xmailer_test 1 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \ttest_config sendemail.xmailer false &&\n \tdo_xmailer_test 0 \"\" &&\n@@ -2080,6 +2102,13 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=' '\n+\ttest_config sendemail.xmailer \"\" &&\n+\tdo_xmailer_test 0 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ 'setup expected-list' '\n \tgit send-email \\\n \t--dry-run \\\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425359","messageId":"patch-03.13-3bbd48dab2-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 03/13] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:08Z","receivedAt":"2021-05-23T08:56:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the already dead code to support \"sendemail.smtpssl\" by finally\nremoving the dead code supporting the configuration option.\n\nIn f6bebd121ac (git-send-email: add support for TLS via\nNet::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was\ndocumented as deprecated, later in 65180c66186 (List send-email config\noptions in config.txt., 2009-07-22) the \"sendemail.smtpssl\"\nconfiguration option was also documented as such.\n\nThen in in 3ff15040e22 (send-email: fix regression in\nsendemail.identity parsing, 2019-05-17) I unintentionally removed\nsupport for it by introducing a bug in read_config().\n\nAs can be seen from the diff context we've already returned unless\n$enc i defined, so it's not possible for us to reach the \"elsif\"\nbranch here. This code was therefore already dead since Git v2.23.0.\n\nSo let's just remove it. We were already 11 years into a stated\ndeprecation period of this variable when 3ff15040e22 landed, now it's\naround 13. Since it hasn't worked anyway for around 2 years it looks\nlike we can safely remove it.\n\nThe --smtp-ssl option is still deprecated, if someone cares they can\nfollow-up and remove that too, but unlike the config option that one\ncould still be in use in the wild. I'm just removing this code that's\nprovably unused already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/sendemail.txt | 3 ---\n git-send-email.perl                | 6 +-----\n 2 files changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt\nindex cbc5af42fd..50baa5d6bf 100644\n--- a/Documentation/config/sendemail.txt\n+++ b/Documentation/config/sendemail.txt\n@@ -8,9 +8,6 @@ sendemail.smtpEncryption::\n \tSee linkgit:git-send-email[1] for description.  Note that this\n \tsetting is not subject to the 'identity' mechanism.\n \n-sendemail.smtpssl (deprecated)::\n-\tDeprecated alias for 'sendemail.smtpEncryption = ssl'.\n-\n sendemail.smtpsslcertpath::\n \tPath to ca-certificates (either a directory or a single file).\n \tSet it to an empty string to disable certificate verification.\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex cffdfdacfb..24d73df5a3 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -383,11 +383,7 @@ sub read_config {\n \t\tmy $enc = Git::config(@repo, $setting);\n \t\treturn unless defined $enc;\n \t\treturn if $configured->{$setting}++;\n-\t\tif (defined $enc) {\n-\t\t\t$smtp_encryption = $enc;\n-\t\t} elsif (Git::config_bool(@repo, \"$prefix.smtpssl\")) {\n-\t\t\t$smtp_encryption = 'ssl';\n-\t\t}\n+\t\t$smtp_encryption = $enc;\n \t}\n }\n \n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425361","messageId":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com","subject":"[PATCH v3 00/13] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:05Z","receivedAt":"2021-05-23T08:56:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"A re-roll of v2[1] which fixes issues pointed out with v2, plus some\nothers I noticed along the way.\n\nThere are now no longer any changes to the \"public\"\nGit::config_regxp() API. Instead it's left bitrotting in our tree at\nthe end of this, having 0 in-tree users (instead of the 1 currently).\n\nWe also handle the -c foo.bar (NULL value) case correctly, per what\nJeff King pointed out. I also found a related issue with\nGIT_TEST_PERL_FATAL_WARNINGS=true, which we can now turn on for\ngit-send-email.perl.\n\n1. https://lore.kernel.org/git/cover-00.10-00000000000-20210520T081826Z-avarab@gmail.com/\n\nÆvar Arnfjörð Bjarmason (13):\n  send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n  send-email tests: test for boolean variables without a value\n  send-email: remove non-working support for \"sendemail.smtpssl\"\n  send-email: refactor sendemail.smtpencryption config parsing\n  send-email: copy \"config_regxp\" into git-send-email.perl\n  send-email: lazily load config for a big speedup\n  send-email: lazily shell out to \"git var\"\n  send-email: use function syntax instead of barewords\n  send-email: get rid of indirect object syntax\n  send-email: lazily load modules for a big speedup\n  perl: lazily load some common Git.pm setup code\n  send-email: move trivial config handling to Perl\n  perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n\n Documentation/config/sendemail.txt |   3 -\n git-send-email.perl                | 174 +++++++++++++++++++----------\n perl/Git.pm                        |  35 +++---\n t/t9001-send-email.sh              |  29 +++++\n 4 files changed, 160 insertions(+), 81 deletions(-)\n\nRange-diff against v2:\n -:  ---------- >  1:  71f890dc60 send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n -:  ---------- >  2:  707c2ca556 send-email tests: test for boolean variables without a value\n 1:  8474acae68 =  3:  3bbd48dab2 send-email: remove non-working support for \"sendemail.smtpssl\"\n 2:  b87f53adbe =  4:  bed0f98d68 send-email: refactor sendemail.smtpencryption config parsing\n -:  ---------- >  5:  c12f69a411 send-email: copy \"config_regxp\" into git-send-email.perl\n 3:  1b27a393ae !  6:  d1c233d251 send-email: lazily load config for a big speedup\n    @@ git-send-email.perl: sub read_config {\n      \t\t\tnext unless defined $v;\n      \t\t\tnext if $configured->{$setting}++;\n      \t\t\t$$target = $v;\n    -@@ git-send-email.perl: sub read_config {\n    - \t}\n    +@@ git-send-email.perl: sub config_regexp {\n    + \treturn @ret;\n      }\n      \n     +# Save ourselves a lot of work of shelling out to 'git config' (it\n     +# parses 'bool' etc.) by only doing so for config keys that exist.\n     +my %known_config_keys;\n     +{\n    -+\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n    ++\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n     +\t@known_config_keys{@known_config_keys} = ();\n     +}\n     +\n    @@ git-send-email.perl: sub read_config {\n      my $rc = GetOptions(\n      \t\"identity=s\" => \\$identity,\n      \t\"no-identity\" => \\$no_identity,\n    -@@ git-send-email.perl: sub read_config {\n    +@@ git-send-email.perl: sub config_regexp {\n      # Now we know enough to read the config\n      {\n          my %configured;\n    @@ git-send-email.perl: sub read_config {\n      }\n      \n      # Begin by accumulating all the variables (defined above), that we will end up\n    -@@ git-send-email.perl: sub read_config {\n    +@@ git-send-email.perl: sub config_regexp {\n          usage();\n      }\n      \n    --if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n    +-if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n     +if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n      \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n      \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n      \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n    -\n    - ## perl/Git.pm ##\n    -@@ perl/Git.pm: sub config_regexp {\n    - \t} catch Git::Error::Command with {\n    - \t\tmy $E = shift;\n    - \t\tif ($E->value() == 1) {\n    --\t\t\tmy @matches = ();\n    --\t\t\treturn @matches;\n    -+\t\t\t# Key(s) not found.\n    -+\t\t\treturn;\n    - \t\t} else {\n    - \t\t\tthrow $E;\n    - \t\t}\n 4:  acee22b77d !  7:  4326c2f99c send-email: lazily shell out to \"git var\"\n    @@ Commit message\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## git-send-email.perl ##\n    -@@ git-send-email.perl: sub read_config {\n    +@@ git-send-email.perl: sub config_regexp {\n      }\n      \n      my ($repoauthor, $repocommitter);\n 5:  f317cd1c01 =  8:  e1fc71e3f9 send-email: use function syntax instead of barewords\n 6:  fc27024f83 =  9:  a806ce06f1 send-email: get rid of indirect object syntax\n 7:  f86f5453d7 ! 10:  aa11439789 send-email: lazily load modules for a big speedup\n    @@ git-send-email.perl\n     @@\n      use 5.008;\n      use strict;\n    - use warnings;\n    + use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n     -use POSIX qw/strftime/;\n     -use Term::ReadLine;\n      use Getopt::Long;\n    @@ git-send-email.perl: sub do_edit {\n      \n      \t# SMTP password masked\n      \tsystem \"stty echo\";\n    -@@ git-send-email.perl: sub read_config {\n    +@@ git-send-email.perl: sub config_regexp {\n      }\n      \n      sub parse_address_line {\n 8:  86641377c0 = 11:  b3b342b173 perl: lazily load some common Git.pm setup code\n 9:  895c9e29a9 ! 12:  950dc0f53d send-email: move trivial config handling to Perl\n    @@ Commit message\n     \n         Optimize the startup time of git-send-email by using an amended\n         config_regexp() function to retrieve the list of config keys and\n    -    values we're interested in. See the earlier \"send-email: lazily load\n    -    config for a big speedup\" commit for why changing its interface is OK.\n    +    values we're interested in.\n     \n         For boolean keys we can handle the [true|false] case ourselves, and\n         the \"--get\" case didn't need any parsing. Let's leave \"--path\" and\n    -    other \"--bool\" cases to \"git config\". As noted in a preceding commit\n    -    we're free to change the config_regexp() function, it's only used by\n    -    \"git send-email\".\n    +    other \"--bool\" cases to \"git config\". I'm not bothering with the\n    +    \"undef\" or \"\" case (true and false, respectively), let's just punt on\n    +    those and others and have \"git config --type=bool\" handle it.\n     \n         This brings the runtime of \"git send-email\" from ~60-~70ms to a very\n         steady ~40ms on my test box. We now run just one \"git config\"\n    @@ Commit message\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    +    diff --git a/git-send-email.perl b/git-send-email.perl\n    +    index 1e9273fd4f..1ea4d9589d 100755\n    +    --- a/git-send-email.perl\n    +    +++ b/git-send-email.perl\n    +    @@ -324,7 +324,11 @@ sub read_config {\n    +                    my $target = $config_bool_settings{$setting};\n    +                    my $key = \"$prefix.$setting\";\n    +                    next unless exists $known_keys->{$key};\n    +    -               my $v = Git::config_bool(@repo, $key);\n    +    +               my $v = (@{$known_keys->{$key}} == 1 &&\n    +    +                        (defined $known_keys->{$key}->[0] &&\n    +    +                         $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n    +    +                       ? $known_keys->{$key}->[0] eq 'true'\n    +    +                       : Git::config_bool(@repo, $key);\n    +                    next unless defined $v;\n    +                    next if $configured->{$setting}++;\n    +                    $$target = $v;\n    +    @@ -353,14 +357,12 @@ sub read_config {\n    +                    my $key = \"$prefix.$setting\";\n    +                    next unless exists $known_keys->{$key};\n    +                    if (ref($target) eq \"ARRAY\") {\n    +    -                       my @values = Git::config(@repo, $key);\n    +    -                       next unless @values;\n    +    +                       my @values = @{$known_keys->{$key}};\n    +                            next if $configured->{$setting}++;\n    +                            @$target = @values;\n    +                    }\n    +                    else {\n    +    -                       my $v = Git::config(@repo, $key);\n    +    -                       next unless defined $v;\n    +    +                       my $v = $known_keys->{$key}->[0];\n    +                            next if $configured->{$setting}++;\n    +                            $$target = $v;\n    +                    }\n    +    @@ -371,12 +373,19 @@ sub config_regexp {\n    +            my ($regex) = @_;\n    +            my @ret;\n    +            eval {\n    +    -               @ret = Git::command(\n    +    +               my $ret = Git::command(\n    +                            'config',\n    +    -                       '--name-only',\n    +    +                       '--null',\n    +                            '--get-regexp',\n    +                            $regex,\n    +                    );\n    +    +               @ret = map {\n    +    +                       # We must always return ($k, $v) here, since\n    +    +                       # empty config values will be just \"key\\0\",\n    +    +                       # not \"key\\nvalue\\0\".\n    +    +                       my ($k, $v) = split /\\n/, $_, 2;\n    +    +                       ($k, $v);\n    +    +               } split /\\0/, $ret;\n    +                    1;\n    +            } or do {\n    +                    # If we have no keys we're OK, otherwise re-throw\n    +    @@ -389,8 +398,10 @@ sub config_regexp {\n    +     # parses 'bool' etc.) by only doing so for config keys that exist.\n    +     my %known_config_keys;\n    +     {\n    +    -       my @known_config_keys = config_regexp(\"^sende?mail[.]\");\n    +    -       @known_config_keys{@known_config_keys} = ();\n    +    +       my @kv = config_regexp(\"^sende?mail[.]\");\n    +    +       while (my ($k, $v) = splice @kv, 0, 2) {\n    +    +               push @{$known_config_keys{$k}} => $v;\n    +    +       }\n    +     }\n    +\n    +     # sendemail.identity yields to --identity. We must parse this\n    +\n      ## git-send-email.perl ##\n     @@ git-send-email.perl: sub read_config {\n      \t\tmy $target = $config_bool_settings{$setting};\n    @@ git-send-email.perl: sub read_config {\n      \t\tnext unless exists $known_keys->{$key};\n     -\t\tmy $v = Git::config_bool(@repo, $key);\n     +\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n    -+\t\t\t $known_keys->{$key}->[0] =~ /^(?:true|false)$/s)\n    ++\t\t\t (defined $known_keys->{$key}->[0] &&\n    ++\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n     +\t\t\t? $known_keys->{$key}->[0] eq 'true'\n     +\t\t\t: Git::config_bool(@repo, $key);\n      \t\tnext unless defined $v;\n    @@ git-send-email.perl: sub read_config {\n      \t\t\tnext if $configured->{$setting}++;\n      \t\t\t$$target = $v;\n      \t\t}\n    -@@ git-send-email.perl: sub read_config {\n    +@@ git-send-email.perl: sub config_regexp {\n    + \tmy ($regex) = @_;\n    + \tmy @ret;\n    + \teval {\n    +-\t\t@ret = Git::command(\n    ++\t\tmy $ret = Git::command(\n    + \t\t\t'config',\n    +-\t\t\t'--name-only',\n    ++\t\t\t'--null',\n    + \t\t\t'--get-regexp',\n    + \t\t\t$regex,\n    + \t\t);\n    ++\t\t@ret = map {\n    ++\t\t\t# We must always return ($k, $v) here, since\n    ++\t\t\t# empty config values will be just \"key\\0\",\n    ++\t\t\t# not \"key\\nvalue\\0\".\n    ++\t\t\tmy ($k, $v) = split /\\n/, $_, 2;\n    ++\t\t\t($k, $v);\n    ++\t\t} split /\\0/, $ret;\n    + \t\t1;\n    + \t} or do {\n    + \t\t# If we have no keys we're OK, otherwise re-throw\n    +@@ git-send-email.perl: sub config_regexp {\n      # parses 'bool' etc.) by only doing so for config keys that exist.\n      my %known_config_keys;\n      {\n    --\tmy @known_config_keys = Git::config_regexp(\"^sende?mail[.]\");\n    +-\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n     -\t@known_config_keys{@known_config_keys} = ();\n    -+\tmy @kv = Git::config_regexp(\"^sende?mail[.]\");\n    ++\tmy @kv = config_regexp(\"^sende?mail[.]\");\n     +\twhile (my ($k, $v) = splice @kv, 0, 2) {\n     +\t\tpush @{$known_config_keys{$k}} => $v;\n     +\t}\n      }\n      \n      # sendemail.identity yields to --identity. We must parse this\n    -\n    - ## perl/Git.pm ##\n    -@@ perl/Git.pm: sub config_int {\n    - =item config_regexp ( RE )\n    - \n    - Retrieve the list of configuration key names matching the regular\n    --expression C<RE>. The return value is a list of strings matching\n    --this regex.\n    -+expression C<RE>. The return value is an ARRAY of key-value pairs.\n    - \n    - =cut\n    - \n    - sub config_regexp {\n    - \tmy ($self, $regex) = _maybe_self(@_);\n    - \ttry {\n    --\t\tmy @cmd = ('config', '--name-only', '--get-regexp', $regex);\n    -+\t\tmy @cmd = ('config', '--null', '--get-regexp', $regex);\n    - \t\tunshift @cmd, $self if $self;\n    --\t\tmy @matches = command(@cmd);\n    --\t\treturn @matches;\n    -+\t\tmy $data = command(@cmd);\n    -+\t\tmy (@kv) = map { split /\\n/, $_, 2 } split /\\0/, $data;\n    -+\t\treturn @kv;\n    - \t} catch Git::Error::Command with {\n    - \t\tmy $E = shift;\n    - \t\tif ($E->value() == 1) {\n10:  97455f993d = 13:  c1d7ea664a perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425362","messageId":"patch-04.13-bed0f98d68-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 04/13] send-email: refactor sendemail.smtpencryption config parsing","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:09Z","receivedAt":"2021-05-23T08:56:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"With the removal of the support for sendemail.smtpssl in the preceding\ncommit the parsing of sendemail.smtpencryption is no longer special,\nand can by moved to %config_settings.\n\nThis gets us rid of an unconditional call to Git::config(), which as\nwe'll see in subsequent commits matters for startup performance.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 9 +--------\n 1 file changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 24d73df5a3..3d8362b5f0 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -277,6 +277,7 @@ sub do_edit {\n );\n \n my %config_settings = (\n+    \"smtpencryption\" => \\$smtp_encryption,\n     \"smtpserver\" => \\$smtp_server,\n     \"smtpserverport\" => \\$smtp_server_port,\n     \"smtpserveroption\" => \\@smtp_server_options,\n@@ -377,14 +378,6 @@ sub read_config {\n \t\t\t$$target = $v;\n \t\t}\n \t}\n-\n-\tif (!defined $smtp_encryption) {\n-\t\tmy $setting = \"$prefix.smtpencryption\";\n-\t\tmy $enc = Git::config(@repo, $setting);\n-\t\treturn unless defined $enc;\n-\t\treturn if $configured->{$setting}++;\n-\t\t$smtp_encryption = $enc;\n-\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425360","messageId":"patch-05.13-c12f69a411-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 05/13] send-email: copy \"config_regxp\" into git-send-email.perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:10Z","receivedAt":"2021-05-23T08:56:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The config_regexp() function was added in dd84e528a3 (git-send-email:\ndie if sendmail.* config is set, 2020-07-23) for use in\ngit-send-email, and it's the only in-tree user of it.\n\nHowever, the consensus is that Git.pm is a public interface, so even\nthough it's a recently added function we can't change it. So let's\ncopy over a minimal version of it to git-send-email.perl itself. In a\nsubsequent commit it'll be changed further for our own use.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++++++-\n 1 file changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 3d8362b5f0..95a69d4c51 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -380,6 +380,24 @@ sub read_config {\n \t}\n }\n \n+sub config_regexp {\n+\tmy ($regex) = @_;\n+\tmy @ret;\n+\teval {\n+\t\t@ret = Git::command(\n+\t\t\t'config',\n+\t\t\t'--name-only',\n+\t\t\t'--get-regexp',\n+\t\t\t$regex,\n+\t\t);\n+\t\t1;\n+\t} or do {\n+\t\t# If we have no keys we're OK, otherwise re-throw\n+\t\tdie $@ if $@->value != 1;\n+\t};\n+\treturn @ret;\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n $identity = Git::config(@repo, \"sendemail.identity\");\n@@ -478,7 +496,7 @@ sub read_config {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425363","messageId":"patch-06.13-d1c233d251-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 06/13] send-email: lazily load config for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:11Z","receivedAt":"2021-05-23T08:56:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Reduce the time it takes git-send-email to get to even the most\ntrivial of tasks (such as serving up its \"-h\" output) by first listing\nconfig keys that exist, and only then only call e.g. \"git config\n--bool\" on them if they do.\n\nOver a lot of runs this speeds the time to \"-h\" up for me from ~250ms\nto ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to\n~20s.\n\nThis introduces a race condition where we'll do the \"wrong\" thing if a\nconfig key were to be inserted between us discovering the list and\ncalling read_config(), i.e. we won't know about the racily added\nkey. In theory this is a change in behavior, in practice it doesn't\nmatter.\n\nThe config_regexp() function being changed here was added in\ndd84e528a34 (git-send-email: die if sendmail.* config is set,\n2020-07-23) for use by git-send-email. So we can change its odd return\nvalue in the case where no values are found by \"git config\". The\ndifference in the *.pm code would matter if it was invoked in scalar\ncontext, but now it no longer is.\n\nArguably this caching belongs in Git.pm itself, but in lieu of\nmodifying it for all its callers let's only do this for \"git\nsend-email\". The other big potential win would be \"git svn\", but\nunlike \"git send-email\" it doesn't check tens of config variables one\nat a time at startup (in my brief testing it doesn't check any).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 35 ++++++++++++++++++++++++++---------\n 1 file changed, 26 insertions(+), 9 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 95a69d4c51..d294681cf3 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -337,11 +337,13 @@ sub signal_handler {\n \n # Read our sendemail.* config\n sub read_config {\n-\tmy ($configured, $prefix) = @_;\n+\tmy ($known_keys, $configured, $prefix) = @_;\n \n \tforeach my $setting (keys %config_bool_settings) {\n \t\tmy $target = $config_bool_settings{$setting};\n-\t\tmy $v = Git::config_bool(@repo, \"$prefix.$setting\");\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n+\t\tmy $v = Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -349,8 +351,10 @@ sub read_config {\n \n \tforeach my $setting (keys %config_path_settings) {\n \t\tmy $target = $config_path_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config_path(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config_path(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n@@ -365,14 +369,16 @@ sub read_config {\n \n \tforeach my $setting (keys %config_settings) {\n \t\tmy $target = $config_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy $v = Git::config(@repo, $key);\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -398,9 +404,20 @@ sub config_regexp {\n \treturn @ret;\n }\n \n+# Save ourselves a lot of work of shelling out to 'git config' (it\n+# parses 'bool' etc.) by only doing so for config keys that exist.\n+my %known_config_keys;\n+{\n+\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n+\t@known_config_keys{@known_config_keys} = ();\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n-$identity = Git::config(@repo, \"sendemail.identity\");\n+{\n+\tmy $key = \"sendemail.identity\";\n+\t$identity = Git::config(@repo, $key) if exists $known_config_keys{$key};\n+}\n my $rc = GetOptions(\n \t\"identity=s\" => \\$identity,\n \t\"no-identity\" => \\$no_identity,\n@@ -411,8 +428,8 @@ sub config_regexp {\n # Now we know enough to read the config\n {\n     my %configured;\n-    read_config(\\%configured, \"sendemail.$identity\") if defined $identity;\n-    read_config(\\%configured, \"sendemail\");\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail.$identity\") if defined $identity;\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail\");\n }\n \n # Begin by accumulating all the variables (defined above), that we will end up\n@@ -496,7 +513,7 @@ sub config_regexp {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425364","messageId":"patch-07.13-4326c2f99c-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 07/13] send-email: lazily shell out to \"git var\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:12Z","receivedAt":"2021-05-23T08:56:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize git-send-email by only shelling out to \"git var\" if we need\nto. This is easily done by re-inventing our own small version of\nperl's Memoize module.\n\nI suppose I could just use Memoize itself, but in a subsequent patch\nI'll be micro-optimizing send-email's use of dependencies. Using\nMemoize is a measly extra 5-10 milliseconds, but as we'll see that'll\nend up mattering for us in the end.\n\nThis brings the runtime of a plain \"send-email\" from around ~160-170ms\nto ~140m-150ms. The runtime of the tests is around the same, or around\n~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++-----\n 1 file changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex d294681cf3..56bd5b0e50 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -578,8 +578,18 @@ sub config_regexp {\n }\n \n my ($repoauthor, $repocommitter);\n-($repoauthor) = Git::ident_person(@repo, 'author');\n-($repocommitter) = Git::ident_person(@repo, 'committer');\n+{\n+\tmy %cache;\n+\tmy ($author, $committer);\n+\tmy $common = sub {\n+\t\tmy ($what) = @_;\n+\t\treturn $cache{$what} if exists $cache{$what};\n+\t\t($cache{$what}) = Git::ident_person(@repo, $what);\n+\t\treturn $cache{$what};\n+\t};\n+\t$repoauthor = sub { $common->('author') };\n+\t$repocommitter = sub { $common->('committer') };\n+}\n \n sub parse_address_line {\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n@@ -767,7 +777,7 @@ sub get_patch_subject {\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n \n-\tmy $tpl_sender = $sender || $repoauthor || $repocommitter || '';\n+\tmy $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';\n \tmy $tpl_subject = $initial_subject || '';\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n@@ -973,7 +983,7 @@ sub file_declares_8bit_cte {\n \t$sender =~ s/^\\s+|\\s+$//g;\n \t($sender) = expand_aliases($sender);\n } else {\n-\t$sender = $repoauthor || $repocommitter || '';\n+\t$sender = $repoauthor->() || $repocommitter->() || '';\n }\n \n # $sender could be an already sanitized address\n@@ -1122,7 +1132,7 @@ sub make_message_id {\n \t$uniq = \"$message_id_stamp-$message_id_serial\";\n \n \tmy $du_part;\n-\tfor ($sender, $repocommitter, $repoauthor) {\n+\tfor ($sender, $repocommitter->(), $repoauthor->()) {\n \t\t$du_part = extract_valid_address(sanitize_address($_));\n \t\tlast if (defined $du_part and $du_part ne '');\n \t}\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425365","messageId":"patch-09.13-a806ce06f1-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 09/13] send-email: get rid of indirect object syntax","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:14Z","receivedAt":"2021-05-23T08:56:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change indirect object syntax such as \"new X ARGS\" to\n\"X->new(ARGS)\". This allows perl to see what \"new\" is at compile-time\nwithout having loaded Term::ReadLine. This doesn't matter now, but\nwill in a subsequent commit when we start lazily loading it.\n\nLet's do the same for the adjacent \"FakeTerm\" package for consistency,\neven though we're not going to conditionally load it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 83f764a8bf..f1542cb3ea 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -194,11 +194,11 @@ sub format_2822_time {\n my @repo = $repo ? ($repo) : ();\n my $term = eval {\n \t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? new Term::ReadLine 'git-send-email', \\*STDIN, \\*STDOUT\n-\t\t: new Term::ReadLine 'git-send-email';\n+\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t: Term::ReadLine->new('git-send-email');\n };\n if ($@) {\n-\t$term = new FakeTerm \"$@: going non-interactive\";\n+\t$term = FakeTerm->new(\"$@: going non-interactive\");\n }\n \n # Behavior modification variables\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425366","messageId":"patch-08.13-e1fc71e3f9-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 08/13] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:13Z","receivedAt":"2021-05-23T08:56:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\ndoesn't have to guess that \"__\" is a function. This makes the code\nmore readable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 56bd5b0e50..83f764a8bf 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -696,7 +696,7 @@ sub is_format_patch_arg {\n \t\tif (defined($format_patch)) {\n \t\t\treturn $format_patch;\n \t\t}\n-\t\tdie sprintf(__ <<EOF, $f, $f);\n+\t\tdie sprintf(__(<<EOF), $f, $f);\n File '%s' exists but it could also be the range of commits\n to produce patches for.  Please disambiguate by...\n \n@@ -782,7 +782,7 @@ sub get_patch_subject {\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n \n-\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __ <<EOT2), <<EOT3;\n+\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __(<<EOT2)), <<EOT3;\n From $tpl_sender # This line is ignored.\n EOT1\n Lines beginning in \"GIT:\" will be removed.\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425367","messageId":"patch-10.13-aa11439789-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 10/13] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:15Z","receivedAt":"2021-05-23T08:56:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the time git-send-email takes to do even the simplest of\nthings (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\nlazily loading the modules it requires.\n\nBefore this change Devel::TraceUse would report 99/97 used modules\nunder NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\nto run t9001-send-email.sh, down from ~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 71 +++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 32 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f1542cb3ea..1e9273fd4f 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -19,20 +19,10 @@\n use 5.008;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n-use POSIX qw/strftime/;\n-use Term::ReadLine;\n use Getopt::Long;\n-use Text::ParseWords;\n-use Term::ANSIColor;\n-use File::Temp qw/ tempdir tempfile /;\n-use File::Spec::Functions qw(catdir catfile);\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n use Git;\n use Git::I18N;\n-use Net::Domain ();\n-use Net::SMTP ();\n-use Git::LoadCPAN::Mail::Address;\n \n Getopt::Long::Configure qw/ pass_through /;\n \n@@ -166,7 +156,6 @@ sub format_2822_time {\n \t\t       );\n }\n \n-my $have_email_valid = eval { require Email::Valid; 1 };\n my $smtp;\n my $auth;\n my $num_sent = 0;\n@@ -192,14 +181,6 @@ sub format_2822_time {\n \n my $repo = eval { Git->repository() };\n my @repo = $repo ? ($repo) : ();\n-my $term = eval {\n-\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n-\t\t: Term::ReadLine->new('git-send-email');\n-};\n-if ($@) {\n-\t$term = FakeTerm->new(\"$@: going non-interactive\");\n-}\n \n # Behavior modification variables\n my ($quiet, $dry_run) = (0, 0);\n@@ -309,9 +290,9 @@ sub do_edit {\n \n # Handle Uncouth Termination\n sub signal_handler {\n-\n \t# Make text normal\n-\tprint color(\"reset\"), \"\\n\";\n+\trequire Term::ANSIColor;\n+\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n \n \t# SMTP password masked\n \tsystem \"stty echo\";\n@@ -592,11 +573,13 @@ sub config_regexp {\n }\n \n sub parse_address_line {\n+\trequire Git::LoadCPAN::Mail::Address;\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n }\n \n sub split_addrs {\n-\treturn quotewords('\\s*,\\s*', 1, @_);\n+\trequire Text::ParseWords;\n+\treturn Text::ParseWords::quotewords('\\s*,\\s*', 1, @_);\n }\n \n my %aliases;\n@@ -645,10 +628,11 @@ sub parse_sendmail_aliases {\n \t\t\ts/\\\\\"/\"/g foreach @addr;\n \t\t\t$aliases{$alias} = \\@addr\n \t\t}}},\n-\tmailrc => sub { my $fh = shift; while (<$fh>) {\n+\tmailrc => sub {\tmy $fh = shift; while (<$fh>) {\n \t\tif (/^alias\\s+(\\S+)\\s+(.*?)\\s*$/) {\n+\t\t\trequire Text::ParseWords;\n \t\t\t# spaces delimit multiple addresses\n-\t\t\t$aliases{$1} = [ quotewords('\\s+', 0, $2) ];\n+\t\t\t$aliases{$1} = [ Text::ParseWords::quotewords('\\s+', 0, $2) ];\n \t\t}}},\n \tpine => sub { my $fh = shift; my $f='\\t[^\\t]*';\n \t        for (my $x = ''; defined($x); $x = $_) {\n@@ -720,7 +704,8 @@ sub is_format_patch_arg {\n \t\topendir my $dh, $f\n \t\t\tor die sprintf(__(\"Failed to opendir %s: %s\"), $f, $!);\n \n-\t\tpush @files, grep { -f $_ } map { catfile($f, $_) }\n+\t\trequire File::Spec;\n+\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n \t\t\t\tsort readdir $dh;\n \t\tclosedir $dh;\n \t} elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {\n@@ -733,7 +718,8 @@ sub is_format_patch_arg {\n if (@rev_list_opts) {\n \tdie __(\"Cannot run git format-patch from outside a repository\\n\")\n \t\tunless $repo;\n-\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n+\trequire File::Temp;\n+\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n }\n \n @files = handle_backup_files(@files);\n@@ -770,9 +756,10 @@ sub get_patch_subject {\n if ($compose) {\n \t# Note that this does not need to be secure, but we will make a small\n \t# effort to have it be unique\n+\trequire File::Temp;\n \t$compose_filename = ($repo ?\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n \topen my $c, \">\", $compose_filename\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n@@ -879,6 +866,19 @@ sub get_patch_subject {\n \tdo_edit(@files);\n }\n \n+sub term {\n+\tmy $term = eval {\n+\t\trequire Term::ReadLine;\n+\t\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n+\t\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t\t: Term::ReadLine->new('git-send-email');\n+\t};\n+\tif ($@) {\n+\t\t$term = FakeTerm->new(\"$@: going non-interactive\");\n+\t}\n+\treturn $term;\n+}\n+\n sub ask {\n \tmy ($prompt, %arg) = @_;\n \tmy $valid_re = $arg{valid_re};\n@@ -886,6 +886,7 @@ sub ask {\n \tmy $confirm_only = $arg{confirm_only};\n \tmy $resp;\n \tmy $i = 0;\n+\tmy $term = term();\n \treturn defined $default ? $default : undef\n \t\tunless defined $term->IN and defined fileno($term->IN) and\n \t\t       defined $term->OUT and defined fileno($term->OUT);\n@@ -1066,6 +1067,7 @@ sub extract_valid_address {\n \treturn $address if ($address =~ /^($local_part_regexp)$/);\n \n \t$address =~ s/^\\s*<(.*)>\\s*$/$1/;\n+\tmy $have_email_valid = eval { require Email::Valid; 1 };\n \tif ($have_email_valid) {\n \t\treturn scalar Email::Valid->address($address);\n \t}\n@@ -1125,7 +1127,8 @@ sub validate_address_list {\n sub make_message_id {\n \tmy $uniq;\n \tif (!defined $message_id_stamp) {\n-\t\t$message_id_stamp = strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n+\t\trequire POSIX;\n+\t\t$message_id_stamp = POSIX::strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n \t\t$message_id_serial = 0;\n \t}\n \t$message_id_serial++;\n@@ -1295,6 +1298,7 @@ sub valid_fqdn {\n sub maildomain_net {\n \tmy $maildomain;\n \n+\trequire Net::Domain;\n \tmy $domain = Net::Domain::domainname();\n \t$maildomain = $domain if valid_fqdn($domain);\n \n@@ -1305,6 +1309,7 @@ sub maildomain_mta {\n \tmy $maildomain;\n \n \tfor my $host (qw(mailhost localhost)) {\n+\t\trequire Net::SMTP;\n \t\tmy $smtp = Net::SMTP->new($host);\n \t\tif (defined $smtp) {\n \t\t\tmy $domain = $smtp->domain;\n@@ -1983,13 +1988,15 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n-\t\tmy $validate_hook = catfile($repo->hooks_path(),\n+\t\trequire File::Spec;\n+\t\tmy $validate_hook = File::Spec->catfile($repo->hooks_path(),\n \t\t\t\t\t    'sendemail-validate');\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n-\t\t\tmy $target = abs_path($fn);\n+\t\t\trequire Cwd;\n+\t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = cwd();\n+\t\t\tmy $cwd_save = Cwd::cwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425368","messageId":"patch-11.13-b3b342b173-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 11/13] perl: lazily load some common Git.pm setup code","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:16Z","receivedAt":"2021-05-23T08:56:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Instead of unconditionally requiring modules such as File::Spec, let's\nonly load them when needed. This speeds up code that only needs a\nsubset of the features Git.pm provides.\n\nThis brings a plain invocation of \"git send-email\" down from 52/37\nloaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes\n~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh\ntest is down to ~13s from ~15s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n perl/Git.pm | 35 ++++++++++++++++++-----------------\n 1 file changed, 18 insertions(+), 17 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 73ebbf80cc..4d048f307b 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -11,9 +11,6 @@ package Git;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n \n-use File::Temp ();\n-use File::Spec ();\n-\n BEGIN {\n \n our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);\n@@ -103,12 +100,9 @@ =head1 DESCRIPTION\n =cut\n \n \n-use Carp qw(carp croak); # but croak is bad - throw instead\n+sub carp { require Carp; goto &Carp::carp }\n+sub croak { require Carp; goto &Carp::croak }\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n-use IPC::Open2 qw(open2);\n-use Fcntl qw(SEEK_SET SEEK_CUR);\n-use Time::Local qw(timegm);\n }\n \n \n@@ -191,13 +185,15 @@ sub repository {\n \t\t\t$dir = undef;\n \t\t};\n \n+\t\trequire Cwd;\n \t\tif ($dir) {\n+\t\t\trequire File::Spec;\n \t\t\tFile::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;\n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \n \t\t\t# If --git-dir went ok, this shouldn't die either.\n \t\t\tmy $prefix = $search->command_oneline('rev-parse', '--show-prefix');\n-\t\t\t$dir = abs_path($opts{Directory}) . '/';\n+\t\t\t$dir = Cwd::abs_path($opts{Directory}) . '/';\n \t\t\tif ($prefix) {\n \t\t\t\tif (substr($dir, -length($prefix)) ne $prefix) {\n \t\t\t\t\tthrow Error::Simple(\"rev-parse confused me - $dir does not have trailing $prefix\");\n@@ -223,7 +219,7 @@ sub repository {\n \t\t\t\tthrow Error::Simple(\"fatal: Not a git repository: $dir\");\n \t\t\t}\n \n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \t\t}\n \n \t\tdelete $opts{Directory};\n@@ -408,10 +404,12 @@ sub command_bidi_pipe {\n \tmy $cwd_save = undef;\n \tif ($self) {\n \t\tshift;\n-\t\t$cwd_save = cwd();\n+\t\trequire Cwd;\n+\t\t$cwd_save = Cwd::cwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n-\t$pid = open2($in, $out, 'git', @_);\n+\trequire IPC::Open2;\n+\t$pid = IPC::Open2::open2($in, $out, 'git', @_);\n \tchdir($cwd_save) if $cwd_save;\n \treturn ($pid, $in, $out, join(' ', @_));\n }\n@@ -538,7 +536,8 @@ sub get_tz_offset {\n \tmy $t = shift || time;\n \tmy @t = localtime($t);\n \t$t[5] += 1900;\n-\tmy $gm = timegm(@t);\n+\trequire Time::Local;\n+\tmy $gm = Time::Local::timegm(@t);\n \tmy $sign = qw( + + - )[ $gm <=> $t ];\n \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n }\n@@ -629,7 +628,8 @@ sub hooks_path {\n \tmy ($self) = @_;\n \n \tmy $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');\n-\tmy $abs = abs_path($dir);\n+\trequire Cwd;\n+\tmy $abs = Cwd::abs_path($dir);\n \treturn $abs;\n }\n \n@@ -1353,6 +1353,7 @@ sub _temp_cache {\n \t\tmy $n = $name;\n \t\t$n =~ s/\\W/_/g; # no strange chars\n \n+\t\trequire File::Temp;\n \t\t($$temp_fd, $fname) = File::Temp::tempfile(\n \t\t\t\"Git_${n}_XXXXXX\", UNLINK => 1, DIR => $tmpdir,\n \t\t\t) or throw Error::Simple(\"couldn't open new temp file\");\n@@ -1375,9 +1376,9 @@ sub temp_reset {\n \n \ttruncate $temp_fd, 0\n \t\tor throw Error::Simple(\"couldn't truncate file\");\n-\tsysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET())\n \t\tor throw Error::Simple(\"couldn't seek to beginning of file\");\n-\tsysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0\n \t\tor throw Error::Simple(\"expected file position to be reset\");\n }\n \n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425369","messageId":"patch-12.13-950dc0f53d-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 12/13] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:17Z","receivedAt":"2021-05-23T08:56:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the startup time of git-send-email by using an amended\nconfig_regexp() function to retrieve the list of config keys and\nvalues we're interested in.\n\nFor boolean keys we can handle the [true|false] case ourselves, and\nthe \"--get\" case didn't need any parsing. Let's leave \"--path\" and\nother \"--bool\" cases to \"git config\". I'm not bothering with the\n\"undef\" or \"\" case (true and false, respectively), let's just punt on\nthose and others and have \"git config --type=bool\" handle it.\n\nThis brings the runtime of \"git send-email\" from ~60-~70ms to a very\nsteady ~40ms on my test box. We now run just one \"git config\"\ninvocation on startup instead of 8, the exact number will differ based\non the local sendemail.* config. I happen to have 8 of those set.\n\nThis brings the runtime of t9001-send-email.sh from ~13s down to ~12s\nfor me. The change there is less impressive as many of those tests set\nvarious config values, and we're also getting to the point of\ndiminishing returns for optimizing \"git send-email\" itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1e9273fd4f..1ea4d9589d 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -324,7 +324,11 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t (defined $known_keys->{$key}->[0] &&\n+\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -353,14 +357,12 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n-\t\t\tnext unless defined $v;\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n \t\t}\n@@ -371,12 +373,19 @@ sub config_regexp {\n \tmy ($regex) = @_;\n \tmy @ret;\n \teval {\n-\t\t@ret = Git::command(\n+\t\tmy $ret = Git::command(\n \t\t\t'config',\n-\t\t\t'--name-only',\n+\t\t\t'--null',\n \t\t\t'--get-regexp',\n \t\t\t$regex,\n \t\t);\n+\t\t@ret = map {\n+\t\t\t# We must always return ($k, $v) here, since\n+\t\t\t# empty config values will be just \"key\\0\",\n+\t\t\t# not \"key\\nvalue\\0\".\n+\t\t\tmy ($k, $v) = split /\\n/, $_, 2;\n+\t\t\t($k, $v);\n+\t\t} split /\\0/, $ret;\n \t\t1;\n \t} or do {\n \t\t# If we have no keys we're OK, otherwise re-throw\n@@ -389,8 +398,10 @@ sub config_regexp {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n\n # sendemail.identity yields to --identity. We must parse this\n---\n git-send-email.perl | 29 ++++++++++++++++++++---------\n 1 file changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1e9273fd4f..1ea4d9589d 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -324,7 +324,11 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t (defined $known_keys->{$key}->[0] &&\n+\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -353,14 +357,12 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n-\t\t\tnext unless defined $v;\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n \t\t}\n@@ -371,12 +373,19 @@ sub config_regexp {\n \tmy ($regex) = @_;\n \tmy @ret;\n \teval {\n-\t\t@ret = Git::command(\n+\t\tmy $ret = Git::command(\n \t\t\t'config',\n-\t\t\t'--name-only',\n+\t\t\t'--null',\n \t\t\t'--get-regexp',\n \t\t\t$regex,\n \t\t);\n+\t\t@ret = map {\n+\t\t\t# We must always return ($k, $v) here, since\n+\t\t\t# empty config values will be just \"key\\0\",\n+\t\t\t# not \"key\\nvalue\\0\".\n+\t\t\tmy ($k, $v) = split /\\n/, $_, 2;\n+\t\t\t($k, $v);\n+\t\t} split /\\0/, $ret;\n \t\t1;\n \t} or do {\n \t\t# If we have no keys we're OK, otherwise re-throw\n@@ -389,8 +398,10 @@ sub config_regexp {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425370","messageId":"patch-13.13-c1d7ea664a-20210523T085231Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v3 13/13] perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-23T08:56:18Z","receivedAt":"2021-05-23T08:56:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"It has been pointed out[1] that cwd() invokes \"pwd(1)\" while getcwd()\nis a Perl-native XS function. For what we're using these for we can\nuse getcwd().\n\nThe performance difference is miniscule, we're saving on the order of\na millisecond or so, see [2] below for the benchmark. I don't think\nthis matters in practice for optimizing git-send-email or perl\nexecution (unlike the patches leading up to this one).\n\nBut let's do it regardless of that, if only so we don't have to think\nabout this as a low-hanging fruit anymore.\n\n1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/\n2.\n    $ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system \"pwd >/dev/null\" }})'\n                (warning: too few iterations for a reliable count)\n                             Rate                  pwd                 cwd    getcwd\n    pwd                     982/s                   --                -48%     -100%\n    cwd                    1890/s                  92%                  --     -100%\n    getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064%        -\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n perl/Git.pm         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1ea4d9589d..c6af05e8a3 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -2007,7 +2007,7 @@ sub validate_patch {\n \t\t\trequire Cwd;\n \t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = Cwd::cwd();\n+\t\t\tmy $cwd_save = Cwd::getcwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 4d048f307b..863bd80694 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -405,7 +405,7 @@ sub command_bidi_pipe {\n \tif ($self) {\n \t\tshift;\n \t\trequire Cwd;\n-\t\t$cwd_save = Cwd::cwd();\n+\t\t$cwd_save = Cwd::getcwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n \trequire IPC::Open2;\n-- \n2.32.0.rc0.406.g05cb3eebfc\n\n"},{"id":"425392","messageId":"xmqqwnrplyns.fsf@gitster.g","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"Re: [PATCH v3 00/13] send-email: various optimizations to speed up by >2x","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-24T01:15:51Z","receivedAt":"2021-05-24T01:15:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> A re-roll of v2[1] which fixes issues pointed out with v2, plus some\n> others I noticed along the way.\n\n\"move trivial\" step does not seem to apply to master, next, or jch\nand it does not seem to apply to master...ab/send-email-optim\neither.\n\nOn top of what commit were these patches designed to be applied?\n\nThanks.\n"},{"id":"425404","messageId":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-0000000000-20210523T085231Z-avarab@gmail.com","subject":"[PATCH v4 00/13] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:49Z","receivedAt":"2021-05-24T07:53:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This v4 fixes an issue in v3 where 12/13 had a \"diff --git\" as part of\nthe commit message (mistake during rebase/squash), which confused \"git\nam\" in trying to apply a diff twice. See <xmqqwnrplyns.fsf@gitster.g>.\n\nÆvar Arnfjörð Bjarmason (13):\n  send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n  send-email tests: test for boolean variables without a value\n  send-email: remove non-working support for \"sendemail.smtpssl\"\n  send-email: refactor sendemail.smtpencryption config parsing\n  send-email: copy \"config_regxp\" into git-send-email.perl\n  send-email: lazily load config for a big speedup\n  send-email: lazily shell out to \"git var\"\n  send-email: use function syntax instead of barewords\n  send-email: get rid of indirect object syntax\n  send-email: lazily load modules for a big speedup\n  perl: lazily load some common Git.pm setup code\n  send-email: move trivial config handling to Perl\n  perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n\n Documentation/config/sendemail.txt |   3 -\n git-send-email.perl                | 174 +++++++++++++++++++----------\n perl/Git.pm                        |  35 +++---\n t/t9001-send-email.sh              |  29 +++++\n 4 files changed, 160 insertions(+), 81 deletions(-)\n\nRange-diff against v3:\n 1:  71f890dc603 =  1:  7140847367c send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n 2:  707c2ca5563 =  2:  d27f3b48f85 send-email tests: test for boolean variables without a value\n 3:  3bbd48dab23 =  3:  a7a21b75f2e send-email: remove non-working support for \"sendemail.smtpssl\"\n 4:  bed0f98d681 =  4:  7356a528589 send-email: refactor sendemail.smtpencryption config parsing\n 5:  c12f69a4110 =  5:  cce0f89143b send-email: copy \"config_regxp\" into git-send-email.perl\n 6:  d1c233d2515 =  6:  8afe8661761 send-email: lazily load config for a big speedup\n 7:  4326c2f99c1 =  7:  491eefde6a2 send-email: lazily shell out to \"git var\"\n 8:  e1fc71e3f90 =  8:  860156013f8 send-email: use function syntax instead of barewords\n 9:  a806ce06f18 =  9:  dd24f1249f5 send-email: get rid of indirect object syntax\n10:  aa11439789d = 10:  61e3e3c93c5 send-email: lazily load modules for a big speedup\n11:  b3b342b173b = 11:  ada34374286 perl: lazily load some common Git.pm setup code\n12:  950dc0f53dd ! 12:  3818000bfba send-email: move trivial config handling to Perl\n    @@ Commit message\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    -    diff --git a/git-send-email.perl b/git-send-email.perl\n    -    index 1e9273fd4f..1ea4d9589d 100755\n    -    --- a/git-send-email.perl\n    -    +++ b/git-send-email.perl\n    -    @@ -324,7 +324,11 @@ sub read_config {\n    -                    my $target = $config_bool_settings{$setting};\n    -                    my $key = \"$prefix.$setting\";\n    -                    next unless exists $known_keys->{$key};\n    -    -               my $v = Git::config_bool(@repo, $key);\n    -    +               my $v = (@{$known_keys->{$key}} == 1 &&\n    -    +                        (defined $known_keys->{$key}->[0] &&\n    -    +                         $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n    -    +                       ? $known_keys->{$key}->[0] eq 'true'\n    -    +                       : Git::config_bool(@repo, $key);\n    -                    next unless defined $v;\n    -                    next if $configured->{$setting}++;\n    -                    $$target = $v;\n    -    @@ -353,14 +357,12 @@ sub read_config {\n    -                    my $key = \"$prefix.$setting\";\n    -                    next unless exists $known_keys->{$key};\n    -                    if (ref($target) eq \"ARRAY\") {\n    -    -                       my @values = Git::config(@repo, $key);\n    -    -                       next unless @values;\n    -    +                       my @values = @{$known_keys->{$key}};\n    -                            next if $configured->{$setting}++;\n    -                            @$target = @values;\n    -                    }\n    -                    else {\n    -    -                       my $v = Git::config(@repo, $key);\n    -    -                       next unless defined $v;\n    -    +                       my $v = $known_keys->{$key}->[0];\n    -                            next if $configured->{$setting}++;\n    -                            $$target = $v;\n    -                    }\n    -    @@ -371,12 +373,19 @@ sub config_regexp {\n    -            my ($regex) = @_;\n    -            my @ret;\n    -            eval {\n    -    -               @ret = Git::command(\n    -    +               my $ret = Git::command(\n    -                            'config',\n    -    -                       '--name-only',\n    -    +                       '--null',\n    -                            '--get-regexp',\n    -                            $regex,\n    -                    );\n    -    +               @ret = map {\n    -    +                       # We must always return ($k, $v) here, since\n    -    +                       # empty config values will be just \"key\\0\",\n    -    +                       # not \"key\\nvalue\\0\".\n    -    +                       my ($k, $v) = split /\\n/, $_, 2;\n    -    +                       ($k, $v);\n    -    +               } split /\\0/, $ret;\n    -                    1;\n    -            } or do {\n    -                    # If we have no keys we're OK, otherwise re-throw\n    -    @@ -389,8 +398,10 @@ sub config_regexp {\n    -     # parses 'bool' etc.) by only doing so for config keys that exist.\n    -     my %known_config_keys;\n    -     {\n    -    -       my @known_config_keys = config_regexp(\"^sende?mail[.]\");\n    -    -       @known_config_keys{@known_config_keys} = ();\n    -    +       my @kv = config_regexp(\"^sende?mail[.]\");\n    -    +       while (my ($k, $v) = splice @kv, 0, 2) {\n    -    +               push @{$known_config_keys{$k}} => $v;\n    -    +       }\n    -     }\n    -\n    -     # sendemail.identity yields to --identity. We must parse this\n    -\n      ## git-send-email.perl ##\n     @@ git-send-email.perl: sub read_config {\n      \t\tmy $target = $config_bool_settings{$setting};\n13:  c1d7ea664ac = 13:  d36b57e429f perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425405","messageId":"patch-01.13-7140847367c-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 01/13] send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:50Z","receivedAt":"2021-05-24T07:53:16Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add support for the \"GIT_TEST_PERL_FATAL_WARNINGS=true\" test mode to\n\"send-email\". This was added to e.g. git-svn in 5338ed2b26 (perl:\ncheck for perl warnings while running tests, 2020-10-21), but not\n\"send-email\". Let's rectify that.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 175da07d946..cffdfdacfb9 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -18,7 +18,7 @@\n \n use 5.008;\n use strict;\n-use warnings;\n+use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n use POSIX qw/strftime/;\n use Term::ReadLine;\n use Getopt::Long;\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425406","messageId":"patch-02.13-d27f3b48f85-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 02/13] send-email tests: test for boolean variables without a value","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:51Z","receivedAt":"2021-05-24T07:53:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The Git.pm code does its own Perl-ifying of boolean variables, let's\nensure that empty values = true for boolean variables, as in the C\ncode.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 65b30353719..6bf79c816a6 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -1349,6 +1349,16 @@ test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '\n \t! grep \"X-Mailer\" stdout\n '\n \n+test_expect_success $PREREQ 'sendemail.identity: bool variable without a value' '\n+\tgit -c sendemail.xmailer \\\n+\t\tsend-email \\\n+\t\t--dry-run \\\n+\t\t--from=\"nobody@example.com\" \\\n+\t\t$patches >stdout &&\n+\tgrep \"To: default@example.com\" stdout &&\n+\tgrep \"X-Mailer\" stdout\n+'\n+\n test_expect_success $PREREQ '--no-to overrides sendemail.to' '\n \tgit send-email \\\n \t\t--dry-run \\\n@@ -2073,6 +2083,18 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=true' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer' '\n+\ttest_when_finished \"test_unconfig sendemail.xmailer\" &&\n+\tcat >>.git/config <<-\\EOF &&\n+\t[sendemail]\n+\t\txmailer\n+\tEOF\n+\ttest_config sendemail.xmailer true &&\n+\tdo_xmailer_test 1 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \ttest_config sendemail.xmailer false &&\n \tdo_xmailer_test 0 \"\" &&\n@@ -2080,6 +2102,13 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=' '\n+\ttest_config sendemail.xmailer \"\" &&\n+\tdo_xmailer_test 0 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ 'setup expected-list' '\n \tgit send-email \\\n \t--dry-run \\\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425407","messageId":"patch-03.13-a7a21b75f2e-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 03/13] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:52Z","receivedAt":"2021-05-24T07:53:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the already dead code to support \"sendemail.smtpssl\" by finally\nremoving the dead code supporting the configuration option.\n\nIn f6bebd121ac (git-send-email: add support for TLS via\nNet::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was\ndocumented as deprecated, later in 65180c66186 (List send-email config\noptions in config.txt., 2009-07-22) the \"sendemail.smtpssl\"\nconfiguration option was also documented as such.\n\nThen in in 3ff15040e22 (send-email: fix regression in\nsendemail.identity parsing, 2019-05-17) I unintentionally removed\nsupport for it by introducing a bug in read_config().\n\nAs can be seen from the diff context we've already returned unless\n$enc i defined, so it's not possible for us to reach the \"elsif\"\nbranch here. This code was therefore already dead since Git v2.23.0.\n\nSo let's just remove it. We were already 11 years into a stated\ndeprecation period of this variable when 3ff15040e22 landed, now it's\naround 13. Since it hasn't worked anyway for around 2 years it looks\nlike we can safely remove it.\n\nThe --smtp-ssl option is still deprecated, if someone cares they can\nfollow-up and remove that too, but unlike the config option that one\ncould still be in use in the wild. I'm just removing this code that's\nprovably unused already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/sendemail.txt | 3 ---\n git-send-email.perl                | 6 +-----\n 2 files changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt\nindex cbc5af42fdf..50baa5d6bfb 100644\n--- a/Documentation/config/sendemail.txt\n+++ b/Documentation/config/sendemail.txt\n@@ -8,9 +8,6 @@ sendemail.smtpEncryption::\n \tSee linkgit:git-send-email[1] for description.  Note that this\n \tsetting is not subject to the 'identity' mechanism.\n \n-sendemail.smtpssl (deprecated)::\n-\tDeprecated alias for 'sendemail.smtpEncryption = ssl'.\n-\n sendemail.smtpsslcertpath::\n \tPath to ca-certificates (either a directory or a single file).\n \tSet it to an empty string to disable certificate verification.\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex cffdfdacfb9..24d73df5a3e 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -383,11 +383,7 @@ sub read_config {\n \t\tmy $enc = Git::config(@repo, $setting);\n \t\treturn unless defined $enc;\n \t\treturn if $configured->{$setting}++;\n-\t\tif (defined $enc) {\n-\t\t\t$smtp_encryption = $enc;\n-\t\t} elsif (Git::config_bool(@repo, \"$prefix.smtpssl\")) {\n-\t\t\t$smtp_encryption = 'ssl';\n-\t\t}\n+\t\t$smtp_encryption = $enc;\n \t}\n }\n \n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425408","messageId":"patch-04.13-7356a528589-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 04/13] send-email: refactor sendemail.smtpencryption config parsing","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:53Z","receivedAt":"2021-05-24T07:53:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"With the removal of the support for sendemail.smtpssl in the preceding\ncommit the parsing of sendemail.smtpencryption is no longer special,\nand can by moved to %config_settings.\n\nThis gets us rid of an unconditional call to Git::config(), which as\nwe'll see in subsequent commits matters for startup performance.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 9 +--------\n 1 file changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 24d73df5a3e..3d8362b5f07 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -277,6 +277,7 @@ sub do_edit {\n );\n \n my %config_settings = (\n+    \"smtpencryption\" => \\$smtp_encryption,\n     \"smtpserver\" => \\$smtp_server,\n     \"smtpserverport\" => \\$smtp_server_port,\n     \"smtpserveroption\" => \\@smtp_server_options,\n@@ -377,14 +378,6 @@ sub read_config {\n \t\t\t$$target = $v;\n \t\t}\n \t}\n-\n-\tif (!defined $smtp_encryption) {\n-\t\tmy $setting = \"$prefix.smtpencryption\";\n-\t\tmy $enc = Git::config(@repo, $setting);\n-\t\treturn unless defined $enc;\n-\t\treturn if $configured->{$setting}++;\n-\t\t$smtp_encryption = $enc;\n-\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425409","messageId":"patch-07.13-491eefde6a2-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 07/13] send-email: lazily shell out to \"git var\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:56Z","receivedAt":"2021-05-24T07:53:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize git-send-email by only shelling out to \"git var\" if we need\nto. This is easily done by re-inventing our own small version of\nperl's Memoize module.\n\nI suppose I could just use Memoize itself, but in a subsequent patch\nI'll be micro-optimizing send-email's use of dependencies. Using\nMemoize is a measly extra 5-10 milliseconds, but as we'll see that'll\nend up mattering for us in the end.\n\nThis brings the runtime of a plain \"send-email\" from around ~160-170ms\nto ~140m-150ms. The runtime of the tests is around the same, or around\n~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++-----\n 1 file changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex d294681cf39..56bd5b0e50c 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -578,8 +578,18 @@ sub config_regexp {\n }\n \n my ($repoauthor, $repocommitter);\n-($repoauthor) = Git::ident_person(@repo, 'author');\n-($repocommitter) = Git::ident_person(@repo, 'committer');\n+{\n+\tmy %cache;\n+\tmy ($author, $committer);\n+\tmy $common = sub {\n+\t\tmy ($what) = @_;\n+\t\treturn $cache{$what} if exists $cache{$what};\n+\t\t($cache{$what}) = Git::ident_person(@repo, $what);\n+\t\treturn $cache{$what};\n+\t};\n+\t$repoauthor = sub { $common->('author') };\n+\t$repocommitter = sub { $common->('committer') };\n+}\n \n sub parse_address_line {\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n@@ -767,7 +777,7 @@ sub get_patch_subject {\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n \n-\tmy $tpl_sender = $sender || $repoauthor || $repocommitter || '';\n+\tmy $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';\n \tmy $tpl_subject = $initial_subject || '';\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n@@ -973,7 +983,7 @@ sub file_declares_8bit_cte {\n \t$sender =~ s/^\\s+|\\s+$//g;\n \t($sender) = expand_aliases($sender);\n } else {\n-\t$sender = $repoauthor || $repocommitter || '';\n+\t$sender = $repoauthor->() || $repocommitter->() || '';\n }\n \n # $sender could be an already sanitized address\n@@ -1122,7 +1132,7 @@ sub make_message_id {\n \t$uniq = \"$message_id_stamp-$message_id_serial\";\n \n \tmy $du_part;\n-\tfor ($sender, $repocommitter, $repoauthor) {\n+\tfor ($sender, $repocommitter->(), $repoauthor->()) {\n \t\t$du_part = extract_valid_address(sanitize_address($_));\n \t\tlast if (defined $du_part and $du_part ne '');\n \t}\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425410","messageId":"patch-06.13-8afe8661761-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 06/13] send-email: lazily load config for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:55Z","receivedAt":"2021-05-24T07:53:27Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Reduce the time it takes git-send-email to get to even the most\ntrivial of tasks (such as serving up its \"-h\" output) by first listing\nconfig keys that exist, and only then only call e.g. \"git config\n--bool\" on them if they do.\n\nOver a lot of runs this speeds the time to \"-h\" up for me from ~250ms\nto ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to\n~20s.\n\nThis introduces a race condition where we'll do the \"wrong\" thing if a\nconfig key were to be inserted between us discovering the list and\ncalling read_config(), i.e. we won't know about the racily added\nkey. In theory this is a change in behavior, in practice it doesn't\nmatter.\n\nThe config_regexp() function being changed here was added in\ndd84e528a34 (git-send-email: die if sendmail.* config is set,\n2020-07-23) for use by git-send-email. So we can change its odd return\nvalue in the case where no values are found by \"git config\". The\ndifference in the *.pm code would matter if it was invoked in scalar\ncontext, but now it no longer is.\n\nArguably this caching belongs in Git.pm itself, but in lieu of\nmodifying it for all its callers let's only do this for \"git\nsend-email\". The other big potential win would be \"git svn\", but\nunlike \"git send-email\" it doesn't check tens of config variables one\nat a time at startup (in my brief testing it doesn't check any).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 35 ++++++++++++++++++++++++++---------\n 1 file changed, 26 insertions(+), 9 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 95a69d4c51f..d294681cf39 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -337,11 +337,13 @@ sub signal_handler {\n \n # Read our sendemail.* config\n sub read_config {\n-\tmy ($configured, $prefix) = @_;\n+\tmy ($known_keys, $configured, $prefix) = @_;\n \n \tforeach my $setting (keys %config_bool_settings) {\n \t\tmy $target = $config_bool_settings{$setting};\n-\t\tmy $v = Git::config_bool(@repo, \"$prefix.$setting\");\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n+\t\tmy $v = Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -349,8 +351,10 @@ sub read_config {\n \n \tforeach my $setting (keys %config_path_settings) {\n \t\tmy $target = $config_path_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config_path(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config_path(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n@@ -365,14 +369,16 @@ sub read_config {\n \n \tforeach my $setting (keys %config_settings) {\n \t\tmy $target = $config_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy $v = Git::config(@repo, $key);\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -398,9 +404,20 @@ sub config_regexp {\n \treturn @ret;\n }\n \n+# Save ourselves a lot of work of shelling out to 'git config' (it\n+# parses 'bool' etc.) by only doing so for config keys that exist.\n+my %known_config_keys;\n+{\n+\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n+\t@known_config_keys{@known_config_keys} = ();\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n-$identity = Git::config(@repo, \"sendemail.identity\");\n+{\n+\tmy $key = \"sendemail.identity\";\n+\t$identity = Git::config(@repo, $key) if exists $known_config_keys{$key};\n+}\n my $rc = GetOptions(\n \t\"identity=s\" => \\$identity,\n \t\"no-identity\" => \\$no_identity,\n@@ -411,8 +428,8 @@ sub config_regexp {\n # Now we know enough to read the config\n {\n     my %configured;\n-    read_config(\\%configured, \"sendemail.$identity\") if defined $identity;\n-    read_config(\\%configured, \"sendemail\");\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail.$identity\") if defined $identity;\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail\");\n }\n \n # Begin by accumulating all the variables (defined above), that we will end up\n@@ -496,7 +513,7 @@ sub config_regexp {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425411","messageId":"patch-05.13-cce0f89143b-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 05/13] send-email: copy \"config_regxp\" into git-send-email.perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:54Z","receivedAt":"2021-05-24T07:53:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The config_regexp() function was added in dd84e528a3 (git-send-email:\ndie if sendmail.* config is set, 2020-07-23) for use in\ngit-send-email, and it's the only in-tree user of it.\n\nHowever, the consensus is that Git.pm is a public interface, so even\nthough it's a recently added function we can't change it. So let's\ncopy over a minimal version of it to git-send-email.perl itself. In a\nsubsequent commit it'll be changed further for our own use.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++++++-\n 1 file changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 3d8362b5f07..95a69d4c51f 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -380,6 +380,24 @@ sub read_config {\n \t}\n }\n \n+sub config_regexp {\n+\tmy ($regex) = @_;\n+\tmy @ret;\n+\teval {\n+\t\t@ret = Git::command(\n+\t\t\t'config',\n+\t\t\t'--name-only',\n+\t\t\t'--get-regexp',\n+\t\t\t$regex,\n+\t\t);\n+\t\t1;\n+\t} or do {\n+\t\t# If we have no keys we're OK, otherwise re-throw\n+\t\tdie $@ if $@->value != 1;\n+\t};\n+\treturn @ret;\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n $identity = Git::config(@repo, \"sendemail.identity\");\n@@ -478,7 +496,7 @@ sub read_config {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425412","messageId":"patch-09.13-dd24f1249f5-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 09/13] send-email: get rid of indirect object syntax","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:58Z","receivedAt":"2021-05-24T07:53:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change indirect object syntax such as \"new X ARGS\" to\n\"X->new(ARGS)\". This allows perl to see what \"new\" is at compile-time\nwithout having loaded Term::ReadLine. This doesn't matter now, but\nwill in a subsequent commit when we start lazily loading it.\n\nLet's do the same for the adjacent \"FakeTerm\" package for consistency,\neven though we're not going to conditionally load it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 83f764a8bf9..f1542cb3ea6 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -194,11 +194,11 @@ sub format_2822_time {\n my @repo = $repo ? ($repo) : ();\n my $term = eval {\n \t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? new Term::ReadLine 'git-send-email', \\*STDIN, \\*STDOUT\n-\t\t: new Term::ReadLine 'git-send-email';\n+\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t: Term::ReadLine->new('git-send-email');\n };\n if ($@) {\n-\t$term = new FakeTerm \"$@: going non-interactive\";\n+\t$term = FakeTerm->new(\"$@: going non-interactive\");\n }\n \n # Behavior modification variables\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425413","messageId":"patch-10.13-61e3e3c93c5-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 10/13] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:59Z","receivedAt":"2021-05-24T07:53:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the time git-send-email takes to do even the simplest of\nthings (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\nlazily loading the modules it requires.\n\nBefore this change Devel::TraceUse would report 99/97 used modules\nunder NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\nto run t9001-send-email.sh, down from ~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 71 +++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 32 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex f1542cb3ea6..1e9273fd4f5 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -19,20 +19,10 @@\n use 5.008;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n-use POSIX qw/strftime/;\n-use Term::ReadLine;\n use Getopt::Long;\n-use Text::ParseWords;\n-use Term::ANSIColor;\n-use File::Temp qw/ tempdir tempfile /;\n-use File::Spec::Functions qw(catdir catfile);\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n use Git;\n use Git::I18N;\n-use Net::Domain ();\n-use Net::SMTP ();\n-use Git::LoadCPAN::Mail::Address;\n \n Getopt::Long::Configure qw/ pass_through /;\n \n@@ -166,7 +156,6 @@ sub format_2822_time {\n \t\t       );\n }\n \n-my $have_email_valid = eval { require Email::Valid; 1 };\n my $smtp;\n my $auth;\n my $num_sent = 0;\n@@ -192,14 +181,6 @@ sub format_2822_time {\n \n my $repo = eval { Git->repository() };\n my @repo = $repo ? ($repo) : ();\n-my $term = eval {\n-\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n-\t\t: Term::ReadLine->new('git-send-email');\n-};\n-if ($@) {\n-\t$term = FakeTerm->new(\"$@: going non-interactive\");\n-}\n \n # Behavior modification variables\n my ($quiet, $dry_run) = (0, 0);\n@@ -309,9 +290,9 @@ sub do_edit {\n \n # Handle Uncouth Termination\n sub signal_handler {\n-\n \t# Make text normal\n-\tprint color(\"reset\"), \"\\n\";\n+\trequire Term::ANSIColor;\n+\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n \n \t# SMTP password masked\n \tsystem \"stty echo\";\n@@ -592,11 +573,13 @@ sub config_regexp {\n }\n \n sub parse_address_line {\n+\trequire Git::LoadCPAN::Mail::Address;\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n }\n \n sub split_addrs {\n-\treturn quotewords('\\s*,\\s*', 1, @_);\n+\trequire Text::ParseWords;\n+\treturn Text::ParseWords::quotewords('\\s*,\\s*', 1, @_);\n }\n \n my %aliases;\n@@ -645,10 +628,11 @@ sub parse_sendmail_aliases {\n \t\t\ts/\\\\\"/\"/g foreach @addr;\n \t\t\t$aliases{$alias} = \\@addr\n \t\t}}},\n-\tmailrc => sub { my $fh = shift; while (<$fh>) {\n+\tmailrc => sub {\tmy $fh = shift; while (<$fh>) {\n \t\tif (/^alias\\s+(\\S+)\\s+(.*?)\\s*$/) {\n+\t\t\trequire Text::ParseWords;\n \t\t\t# spaces delimit multiple addresses\n-\t\t\t$aliases{$1} = [ quotewords('\\s+', 0, $2) ];\n+\t\t\t$aliases{$1} = [ Text::ParseWords::quotewords('\\s+', 0, $2) ];\n \t\t}}},\n \tpine => sub { my $fh = shift; my $f='\\t[^\\t]*';\n \t        for (my $x = ''; defined($x); $x = $_) {\n@@ -720,7 +704,8 @@ sub is_format_patch_arg {\n \t\topendir my $dh, $f\n \t\t\tor die sprintf(__(\"Failed to opendir %s: %s\"), $f, $!);\n \n-\t\tpush @files, grep { -f $_ } map { catfile($f, $_) }\n+\t\trequire File::Spec;\n+\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n \t\t\t\tsort readdir $dh;\n \t\tclosedir $dh;\n \t} elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {\n@@ -733,7 +718,8 @@ sub is_format_patch_arg {\n if (@rev_list_opts) {\n \tdie __(\"Cannot run git format-patch from outside a repository\\n\")\n \t\tunless $repo;\n-\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n+\trequire File::Temp;\n+\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n }\n \n @files = handle_backup_files(@files);\n@@ -770,9 +756,10 @@ sub get_patch_subject {\n if ($compose) {\n \t# Note that this does not need to be secure, but we will make a small\n \t# effort to have it be unique\n+\trequire File::Temp;\n \t$compose_filename = ($repo ?\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n \topen my $c, \">\", $compose_filename\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n@@ -879,6 +866,19 @@ sub get_patch_subject {\n \tdo_edit(@files);\n }\n \n+sub term {\n+\tmy $term = eval {\n+\t\trequire Term::ReadLine;\n+\t\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n+\t\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t\t: Term::ReadLine->new('git-send-email');\n+\t};\n+\tif ($@) {\n+\t\t$term = FakeTerm->new(\"$@: going non-interactive\");\n+\t}\n+\treturn $term;\n+}\n+\n sub ask {\n \tmy ($prompt, %arg) = @_;\n \tmy $valid_re = $arg{valid_re};\n@@ -886,6 +886,7 @@ sub ask {\n \tmy $confirm_only = $arg{confirm_only};\n \tmy $resp;\n \tmy $i = 0;\n+\tmy $term = term();\n \treturn defined $default ? $default : undef\n \t\tunless defined $term->IN and defined fileno($term->IN) and\n \t\t       defined $term->OUT and defined fileno($term->OUT);\n@@ -1066,6 +1067,7 @@ sub extract_valid_address {\n \treturn $address if ($address =~ /^($local_part_regexp)$/);\n \n \t$address =~ s/^\\s*<(.*)>\\s*$/$1/;\n+\tmy $have_email_valid = eval { require Email::Valid; 1 };\n \tif ($have_email_valid) {\n \t\treturn scalar Email::Valid->address($address);\n \t}\n@@ -1125,7 +1127,8 @@ sub validate_address_list {\n sub make_message_id {\n \tmy $uniq;\n \tif (!defined $message_id_stamp) {\n-\t\t$message_id_stamp = strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n+\t\trequire POSIX;\n+\t\t$message_id_stamp = POSIX::strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n \t\t$message_id_serial = 0;\n \t}\n \t$message_id_serial++;\n@@ -1295,6 +1298,7 @@ sub valid_fqdn {\n sub maildomain_net {\n \tmy $maildomain;\n \n+\trequire Net::Domain;\n \tmy $domain = Net::Domain::domainname();\n \t$maildomain = $domain if valid_fqdn($domain);\n \n@@ -1305,6 +1309,7 @@ sub maildomain_mta {\n \tmy $maildomain;\n \n \tfor my $host (qw(mailhost localhost)) {\n+\t\trequire Net::SMTP;\n \t\tmy $smtp = Net::SMTP->new($host);\n \t\tif (defined $smtp) {\n \t\t\tmy $domain = $smtp->domain;\n@@ -1983,13 +1988,15 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n-\t\tmy $validate_hook = catfile($repo->hooks_path(),\n+\t\trequire File::Spec;\n+\t\tmy $validate_hook = File::Spec->catfile($repo->hooks_path(),\n \t\t\t\t\t    'sendemail-validate');\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n-\t\t\tmy $target = abs_path($fn);\n+\t\t\trequire Cwd;\n+\t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = cwd();\n+\t\t\tmy $cwd_save = Cwd::cwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425414","messageId":"patch-11.13-ada34374286-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 11/13] perl: lazily load some common Git.pm setup code","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:53:00Z","receivedAt":"2021-05-24T07:53:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Instead of unconditionally requiring modules such as File::Spec, let's\nonly load them when needed. This speeds up code that only needs a\nsubset of the features Git.pm provides.\n\nThis brings a plain invocation of \"git send-email\" down from 52/37\nloaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes\n~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh\ntest is down to ~13s from ~15s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n perl/Git.pm | 35 ++++++++++++++++++-----------------\n 1 file changed, 18 insertions(+), 17 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 73ebbf80cc6..4d048f307b3 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -11,9 +11,6 @@ package Git;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n \n-use File::Temp ();\n-use File::Spec ();\n-\n BEGIN {\n \n our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);\n@@ -103,12 +100,9 @@ =head1 DESCRIPTION\n =cut\n \n \n-use Carp qw(carp croak); # but croak is bad - throw instead\n+sub carp { require Carp; goto &Carp::carp }\n+sub croak { require Carp; goto &Carp::croak }\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n-use IPC::Open2 qw(open2);\n-use Fcntl qw(SEEK_SET SEEK_CUR);\n-use Time::Local qw(timegm);\n }\n \n \n@@ -191,13 +185,15 @@ sub repository {\n \t\t\t$dir = undef;\n \t\t};\n \n+\t\trequire Cwd;\n \t\tif ($dir) {\n+\t\t\trequire File::Spec;\n \t\t\tFile::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;\n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \n \t\t\t# If --git-dir went ok, this shouldn't die either.\n \t\t\tmy $prefix = $search->command_oneline('rev-parse', '--show-prefix');\n-\t\t\t$dir = abs_path($opts{Directory}) . '/';\n+\t\t\t$dir = Cwd::abs_path($opts{Directory}) . '/';\n \t\t\tif ($prefix) {\n \t\t\t\tif (substr($dir, -length($prefix)) ne $prefix) {\n \t\t\t\t\tthrow Error::Simple(\"rev-parse confused me - $dir does not have trailing $prefix\");\n@@ -223,7 +219,7 @@ sub repository {\n \t\t\t\tthrow Error::Simple(\"fatal: Not a git repository: $dir\");\n \t\t\t}\n \n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \t\t}\n \n \t\tdelete $opts{Directory};\n@@ -408,10 +404,12 @@ sub command_bidi_pipe {\n \tmy $cwd_save = undef;\n \tif ($self) {\n \t\tshift;\n-\t\t$cwd_save = cwd();\n+\t\trequire Cwd;\n+\t\t$cwd_save = Cwd::cwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n-\t$pid = open2($in, $out, 'git', @_);\n+\trequire IPC::Open2;\n+\t$pid = IPC::Open2::open2($in, $out, 'git', @_);\n \tchdir($cwd_save) if $cwd_save;\n \treturn ($pid, $in, $out, join(' ', @_));\n }\n@@ -538,7 +536,8 @@ sub get_tz_offset {\n \tmy $t = shift || time;\n \tmy @t = localtime($t);\n \t$t[5] += 1900;\n-\tmy $gm = timegm(@t);\n+\trequire Time::Local;\n+\tmy $gm = Time::Local::timegm(@t);\n \tmy $sign = qw( + + - )[ $gm <=> $t ];\n \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n }\n@@ -629,7 +628,8 @@ sub hooks_path {\n \tmy ($self) = @_;\n \n \tmy $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');\n-\tmy $abs = abs_path($dir);\n+\trequire Cwd;\n+\tmy $abs = Cwd::abs_path($dir);\n \treturn $abs;\n }\n \n@@ -1353,6 +1353,7 @@ sub _temp_cache {\n \t\tmy $n = $name;\n \t\t$n =~ s/\\W/_/g; # no strange chars\n \n+\t\trequire File::Temp;\n \t\t($$temp_fd, $fname) = File::Temp::tempfile(\n \t\t\t\"Git_${n}_XXXXXX\", UNLINK => 1, DIR => $tmpdir,\n \t\t\t) or throw Error::Simple(\"couldn't open new temp file\");\n@@ -1375,9 +1376,9 @@ sub temp_reset {\n \n \ttruncate $temp_fd, 0\n \t\tor throw Error::Simple(\"couldn't truncate file\");\n-\tsysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET())\n \t\tor throw Error::Simple(\"couldn't seek to beginning of file\");\n-\tsysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0\n \t\tor throw Error::Simple(\"expected file position to be reset\");\n }\n \n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425415","messageId":"patch-12.13-3818000bfba-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 12/13] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:53:01Z","receivedAt":"2021-05-24T07:53:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the startup time of git-send-email by using an amended\nconfig_regexp() function to retrieve the list of config keys and\nvalues we're interested in.\n\nFor boolean keys we can handle the [true|false] case ourselves, and\nthe \"--get\" case didn't need any parsing. Let's leave \"--path\" and\nother \"--bool\" cases to \"git config\". I'm not bothering with the\n\"undef\" or \"\" case (true and false, respectively), let's just punt on\nthose and others and have \"git config --type=bool\" handle it.\n\nThis brings the runtime of \"git send-email\" from ~60-~70ms to a very\nsteady ~40ms on my test box. We now run just one \"git config\"\ninvocation on startup instead of 8, the exact number will differ based\non the local sendemail.* config. I happen to have 8 of those set.\n\nThis brings the runtime of t9001-send-email.sh from ~13s down to ~12s\nfor me. The change there is less impressive as many of those tests set\nvarious config values, and we're also getting to the point of\ndiminishing returns for optimizing \"git send-email\" itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 29 ++++++++++++++++++++---------\n 1 file changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1e9273fd4f5..1ea4d9589d8 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -324,7 +324,11 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t (defined $known_keys->{$key}->[0] &&\n+\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -353,14 +357,12 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n-\t\t\tnext unless defined $v;\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n \t\t}\n@@ -371,12 +373,19 @@ sub config_regexp {\n \tmy ($regex) = @_;\n \tmy @ret;\n \teval {\n-\t\t@ret = Git::command(\n+\t\tmy $ret = Git::command(\n \t\t\t'config',\n-\t\t\t'--name-only',\n+\t\t\t'--null',\n \t\t\t'--get-regexp',\n \t\t\t$regex,\n \t\t);\n+\t\t@ret = map {\n+\t\t\t# We must always return ($k, $v) here, since\n+\t\t\t# empty config values will be just \"key\\0\",\n+\t\t\t# not \"key\\nvalue\\0\".\n+\t\t\tmy ($k, $v) = split /\\n/, $_, 2;\n+\t\t\t($k, $v);\n+\t\t} split /\\0/, $ret;\n \t\t1;\n \t} or do {\n \t\t# If we have no keys we're OK, otherwise re-throw\n@@ -389,8 +398,10 @@ sub config_regexp {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425416","messageId":"patch-08.13-860156013f8-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 08/13] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:52:57Z","receivedAt":"2021-05-24T07:53:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\ndoesn't have to guess that \"__\" is a function. This makes the code\nmore readable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 56bd5b0e50c..83f764a8bf9 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -696,7 +696,7 @@ sub is_format_patch_arg {\n \t\tif (defined($format_patch)) {\n \t\t\treturn $format_patch;\n \t\t}\n-\t\tdie sprintf(__ <<EOF, $f, $f);\n+\t\tdie sprintf(__(<<EOF), $f, $f);\n File '%s' exists but it could also be the range of commits\n to produce patches for.  Please disambiguate by...\n \n@@ -782,7 +782,7 @@ sub get_patch_subject {\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n \n-\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __ <<EOT2), <<EOT3;\n+\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __(<<EOT2)), <<EOT3;\n From $tpl_sender # This line is ignored.\n EOT1\n Lines beginning in \"GIT:\" will be removed.\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425417","messageId":"patch-13.13-d36b57e429f-20210524T074932Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v4 13/13] perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-24T07:53:02Z","receivedAt":"2021-05-24T07:53:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"It has been pointed out[1] that cwd() invokes \"pwd(1)\" while getcwd()\nis a Perl-native XS function. For what we're using these for we can\nuse getcwd().\n\nThe performance difference is miniscule, we're saving on the order of\na millisecond or so, see [2] below for the benchmark. I don't think\nthis matters in practice for optimizing git-send-email or perl\nexecution (unlike the patches leading up to this one).\n\nBut let's do it regardless of that, if only so we don't have to think\nabout this as a low-hanging fruit anymore.\n\n1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/\n2.\n    $ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system \"pwd >/dev/null\" }})'\n                (warning: too few iterations for a reliable count)\n                             Rate                  pwd                 cwd    getcwd\n    pwd                     982/s                   --                -48%     -100%\n    cwd                    1890/s                  92%                  --     -100%\n    getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064%        -\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n perl/Git.pm         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1ea4d9589d8..c6af05e8a31 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -2007,7 +2007,7 @@ sub validate_patch {\n \t\t\trequire Cwd;\n \t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = Cwd::cwd();\n+\t\t\tmy $cwd_save = Cwd::getcwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 4d048f307b3..863bd80694c 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -405,7 +405,7 @@ sub command_bidi_pipe {\n \tif ($self) {\n \t\tshift;\n \t\trequire Cwd;\n-\t\t$cwd_save = Cwd::cwd();\n+\t\t$cwd_save = Cwd::getcwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n \trequire IPC::Open2;\n-- \n2.32.0.rc1.385.g46e826f1e55\n\n"},{"id":"425644","messageId":"xmqqv9759e0z.fsf@gitster.g","threadId":"55673","inReplyTo":"patch-10.13-61e3e3c93c5-20210524T074932Z-avarab@gmail.com","subject":"Re: [PATCH v4 10/13] send-email: lazily load modules for a big speedup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-05-27T01:11:24Z","receivedAt":"2021-05-27T01:11:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> -use File::Temp qw/ tempdir tempfile /;\n> -use File::Spec::Functions qw(catdir catfile);\n> ...\n> +\t\trequire File::Spec;\n> +\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n>  \t\t\t\tsort readdir $dh;\n> ...\n> -\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n> +\trequire File::Temp;\n> +\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n\nI suspect I would not be the only one who finds it curious to have\nthe distinction between the use of \"->\" in File::Spec->catfile() and\n\"::\" in File::Temp::tempdir (and all the other function calls\ntouched by this patch).\n\nWouldn't it be more faithful to the original to do:\n\n    require File::Spec::Functions;\n    push @files, ... map { File::Spec::Functions::catfile($f, $_) }\n\nin the first hunk?  It would save the time readers of \"git log -p\"\nhas to spend on wondering why \"catfile\" is an oddball.\n\nOf course \"man File::Spec\" would educate readers where the\ndifference comes from, but it is not immediately obvious to me why\nwe want to switch in a \"lazily load for speedup\" that is supposed to\nbe optimization-only, even if the class method ends up calling the\nsame underlying thing.\n\nThanks.\n\n"},{"id":"425655","messageId":"CABPp-BEtef+EzZTAQYutU67V4-=EiXHBH7wLm1ZEE5N+_NA5Yw@mail.gmail.com","threadId":"55673","inReplyTo":"cover-0.9-0000000000-20210512T132955Z-avarab@gmail.com","subject":"Re: [PATCH 0/9] send-email: various optimizations to speed up by >2x","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-05-27T07:21:24Z","receivedAt":"2021-05-27T07:21:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, May 12, 2021 at 6:50 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> As noted in the subject this speeds up git-send-email invocations by\n> ~2x or more, and brings the very slow t9001 test from running in ~26s\n> on my box to ~12s. It's no longer consistently the slowest test I run.\n>\n> This is basically done in two ways: We lazily invoke \"git config\" to\n> get config, before it's very eager, and deferring Perl compilation\n> with s/use/require/g.\n\nI know I'm very late to the party, but I just wanted to comment that\nthis is super cool.  Thanks for speeding this up; some really good\nfinds here.\n"},{"id":"425664","messageId":"8735u8mmj9.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"xmqqv9759e0z.fsf@gitster.g","subject":"Re: [PATCH v4 10/13] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-27T11:36:37Z","receivedAt":"2021-05-27T11:41:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, May 27 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n>\n>> -use File::Temp qw/ tempdir tempfile /;\n>> -use File::Spec::Functions qw(catdir catfile);\n>> ...\n>> +\t\trequire File::Spec;\n>> +\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n>>  \t\t\t\tsort readdir $dh;\n>> ...\n>> -\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n>> +\trequire File::Temp;\n>> +\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n>\n> I suspect I would not be the only one who finds it curious to have\n> the distinction between the use of \"->\" in File::Spec->catfile() and\n> \"::\" in File::Temp::tempdir (and all the other function calls\n> touched by this patch).\n>\n> Wouldn't it be more faithful to the original to do:\n>\n>     require File::Spec::Functions;\n>     push @files, ... map { File::Spec::Functions::catfile($f, $_) }\n>\n> in the first hunk?  It would save the time readers of \"git log -p\"\n> has to spend on wondering why \"catfile\" is an oddball.\n>\n> Of course \"man File::Spec\" would educate readers where the\n> difference comes from, but it is not immediately obvious to me why\n> we want to switch in a \"lazily load for speedup\" that is supposed to\n> be optimization-only, even if the class method ends up calling the\n> same underlying thing.\n\nIt was idiomatic to me so I didn't think of explaining it. Yes it looks\nodd, but it's the most straightforward and correct thing to do in this\ns/use/require/ change.\n\nThe reason to use the File::Spec::Functions wrapper is because you want\nFile::Spec class methods in your namespace. I.e. to do catfile() instead\nof File::Spec->catfile().\n\nTo do that requires importing the \"catfile\" symbol. Since we're doing a\ns/use/requires/ here we won't do that (symbols like that are\ncompile-time), so we'd need to call File::Spec::Functions::catfile() as\nyou point out.\n\nExcept the whole point of that Functions.pm package is to not need that\nfully-qualified name, so once we're doing that we might as well call\nFile::Spec->catfile() instead.\n\nThe implementation of File::Spec::Functions is to literally give you a\ngeneraler boilerplate function in your namespace that calls\nFile::Spec::catfile(\"File::Spec\", <your argument here>), which is what\nthe \"->\" class method syntax does implicitly. Calling that wrapper\ndoesn't make sense here.\n"},{"id":"425687","messageId":"YK/BbcroFTDXDdIm@coredump.intra.peff.net","threadId":"55673","inReplyTo":"patch-12.13-3818000bfba-20210524T074932Z-avarab@gmail.com","subject":"Re: [PATCH v4 12/13] send-email: move trivial config handling to Perl","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-27T15:57:33Z","receivedAt":"2021-05-27T15:57:47Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 24, 2021 at 09:53:01AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> diff --git a/git-send-email.perl b/git-send-email.perl\n> index 1e9273fd4f5..1ea4d9589d8 100755\n> --- a/git-send-email.perl\n> +++ b/git-send-email.perl\n> @@ -324,7 +324,11 @@ sub read_config {\n>  \t\tmy $target = $config_bool_settings{$setting};\n>  \t\tmy $key = \"$prefix.$setting\";\n>  \t\tnext unless exists $known_keys->{$key};\n> -\t\tmy $v = Git::config_bool(@repo, $key);\n> +\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n> +\t\t\t (defined $known_keys->{$key}->[0] &&\n> +\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n> +\t\t\t? $known_keys->{$key}->[0] eq 'true'\n> +\t\t\t: Git::config_bool(@repo, $key);\n\nThanks for addressing this. It looks like an undefined value will kick\nback to the Git::config_bool() case. That's probably fine, as I'd think\nit's relatively rare (and this is all just optimization anyway).\n\n> @@ -353,14 +357,12 @@ sub read_config {\n>  \t\tmy $key = \"$prefix.$setting\";\n>  \t\tnext unless exists $known_keys->{$key};\n>  \t\tif (ref($target) eq \"ARRAY\") {\n> -\t\t\tmy @values = Git::config(@repo, $key);\n> -\t\t\tnext unless @values;\n> +\t\t\tmy @values = @{$known_keys->{$key}};\n>  \t\t\tnext if $configured->{$setting}++;\n>  \t\t\t@$target = @values;\n>  \t\t}\n\nI do wonder what happens for non-bool values here. I.e., would we return\na list that contains undef? Before your change, the value comes from\nGit::config(), and now it comes from our pre-read $known_keys.\n\nIt seems fine either way:\n\n  $ git -c sendemail.smtpsslcertpath send-email -1\n  error: missing value for 'sendemail.smtpsslcertpath'\n  fatal: unable to parse command-line config\n  config --path --get sendemail.smtpsslcertpath: command returned error: 128\n\nbut I didn't carefully follow all the paths that config can take. So I\nraise it only as a potential issue. Your response is hopefully \"yes, I\nthought of that, and it is fine.\" :)\n\n-Peff\n"},{"id":"425688","messageId":"YK/CEMd52k+cK0dz@coredump.intra.peff.net","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"Re: [PATCH v4 00/13] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-27T16:00:16Z","receivedAt":"2021-05-27T16:00:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 24, 2021 at 09:52:49AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> This v4 fixes an issue in v3 where 12/13 had a \"diff --git\" as part of\n> the commit message (mistake during rebase/squash), which confused \"git\n> am\" in trying to apply a diff twice. See <xmqqwnrplyns.fsf@gitster.g>.\n\nI raised a probably-not-a-problem question in patch 12. Assuming it's\nindeed not-a-problem, this all looks good to me.\n\nThanks for addressing my concerns about Git.pm stability.\n\n-Peff\n\nPS I hit some mild conflicts applying this on top of master because\n   7cbc0455cc (send-email: move \"hooks_path\" invocation to\n   git-send-email.perl, 2021-05-26) graduated in the meantime. I expect\n   Junio prefers it as you have it here, since he tends to use the\n   original base from previous rounds, but just a hint to any other\n   reviewers.\n"},{"id":"425750","messageId":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210524T074932Z-avarab@gmail.com","subject":"[PATCH v5 00/13] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:39Z","receivedAt":"2021-05-28T09:23:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Hopefully the final iteration. Updates a commit message to explain why\nI moved away from File::Spec::Functions, rebases on master, and\nexplains and deals with the \"undef in config\" issue Jeff King noted.\n\nÆvar Arnfjörð Bjarmason (13):\n  send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n  send-email tests: test for boolean variables without a value\n  send-email: remove non-working support for \"sendemail.smtpssl\"\n  send-email: refactor sendemail.smtpencryption config parsing\n  send-email: copy \"config_regxp\" into git-send-email.perl\n  send-email: lazily load config for a big speedup\n  send-email: lazily shell out to \"git var\"\n  send-email: use function syntax instead of barewords\n  send-email: get rid of indirect object syntax\n  send-email: lazily load modules for a big speedup\n  perl: lazily load some common Git.pm setup code\n  send-email: move trivial config handling to Perl\n  perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n\n Documentation/config/sendemail.txt |   3 -\n git-send-email.perl                | 174 +++++++++++++++++++----------\n perl/Git.pm                        |  32 +++---\n t/t9001-send-email.sh              |  29 +++++\n 4 files changed, 159 insertions(+), 79 deletions(-)\n\nRange-diff against v4:\n 1:  7140847367c =  1:  81025b48f1c send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true\n 2:  d27f3b48f85 =  2:  16277bd1082 send-email tests: test for boolean variables without a value\n 3:  a7a21b75f2e =  3:  e3e3e6415d2 send-email: remove non-working support for \"sendemail.smtpssl\"\n 4:  7356a528589 =  4:  961ca4c2b2a send-email: refactor sendemail.smtpencryption config parsing\n 5:  cce0f89143b =  5:  f2bd12728a1 send-email: copy \"config_regxp\" into git-send-email.perl\n 6:  8afe8661761 =  6:  4cf70c6f97e send-email: lazily load config for a big speedup\n 7:  491eefde6a2 =  7:  bd0d9535718 send-email: lazily shell out to \"git var\"\n 8:  860156013f8 =  8:  f1a879a8ae9 send-email: use function syntax instead of barewords\n 9:  dd24f1249f5 =  9:  881b1093409 send-email: get rid of indirect object syntax\n10:  61e3e3c93c5 ! 10:  9f21bc6e6f2 send-email: lazily load modules for a big speedup\n    @@ Commit message\n         under NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\n         to run t9001-send-email.sh, down from ~20s.\n     \n    +    Changing File::Spec::Functions::{catdir,catfile} to invoking class\n    +    methods on File::Spec itself is idiomatic. See [1] for a more\n    +    elaborate explanation, the resulting code behaves the same way, just\n    +    without the now-pointless function wrapper.\n    +\n    +    1. http://lore.kernel.org/git/8735u8mmj9.fsf@evledraar.gmail.com\n    +\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## git-send-email.perl ##\n    @@ git-send-email.perl: sub maildomain_mta {\n      \t\tif (defined $smtp) {\n      \t\t\tmy $domain = $smtp->domain;\n     @@ git-send-email.perl: sub validate_patch {\n    - \tmy ($fn, $xfer_encoding) = @_;\n      \n      \tif ($repo) {\n    --\t\tmy $validate_hook = catfile($repo->hooks_path(),\n    + \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n    +-\t\tmy $validate_hook = catfile($hooks_path,\n     +\t\trequire File::Spec;\n    -+\t\tmy $validate_hook = File::Spec->catfile($repo->hooks_path(),\n    ++\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n      \t\t\t\t\t    'sendemail-validate');\n      \t\tmy $hook_error;\n      \t\tif (-x $validate_hook) {\n11:  ada34374286 ! 11:  66f68e38c16 perl: lazily load some common Git.pm setup code\n    @@ perl/Git.pm: sub get_tz_offset {\n      \tmy $sign = qw( + + - )[ $gm <=> $t ];\n      \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n      }\n    -@@ perl/Git.pm: sub hooks_path {\n    - \tmy ($self) = @_;\n    - \n    - \tmy $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks');\n    --\tmy $abs = abs_path($dir);\n    -+\trequire Cwd;\n    -+\tmy $abs = Cwd::abs_path($dir);\n    - \treturn $abs;\n    - }\n    - \n     @@ perl/Git.pm: sub _temp_cache {\n      \t\tmy $n = $name;\n      \t\t$n =~ s/\\W/_/g; # no strange chars\n12:  3818000bfba ! 12:  f605b5ae49f send-email: move trivial config handling to Perl\n    @@ Commit message\n         \"undef\" or \"\" case (true and false, respectively), let's just punt on\n         those and others and have \"git config --type=bool\" handle it.\n     \n    +    The \"grep { defined } @values\" here covers a rather subtle case. For\n    +    list values such as sendemail.to it is possible as with any other\n    +    config key to provide a plain \"-c sendemail.to\", i.e. to set the key\n    +    as a boolean true. In that case the Git::config() API will return an\n    +    empty string, but this new parser will correctly return \"undef\".\n    +\n    +    However, that means we can end up with \"undef\" in the middle of a\n    +    list. E.g. for sendemail.smtpserveroption in conjuction with\n    +    sendemail.smtpserver as a path this would have produce a warning. For\n    +    most of the other keys we'd behave the same despite the subtle change\n    +    in the value, e.g. sendemail.to would behave the same because\n    +    Mail::Address->parse() happens to return an empty list if fed\n    +    \"undef\". For the boolean values we were already prepared to handle\n    +    these variables being initialized as undef anyway.\n    +\n         This brings the runtime of \"git send-email\" from ~60-~70ms to a very\n         steady ~40ms on my test box. We now run just one \"git config\"\n         invocation on startup instead of 8, the exact number will differ based\n    @@ git-send-email.perl: sub read_config {\n     -\t\t\tmy @values = Git::config(@repo, $key);\n     -\t\t\tnext unless @values;\n     +\t\t\tmy @values = @{$known_keys->{$key}};\n    ++\t\t\t@values = grep { defined } @values;\n      \t\t\tnext if $configured->{$setting}++;\n      \t\t\t@$target = @values;\n      \t\t}\n      \t\telse {\n     -\t\t\tmy $v = Git::config(@repo, $key);\n    --\t\t\tnext unless defined $v;\n     +\t\t\tmy $v = $known_keys->{$key}->[0];\n    + \t\t\tnext unless defined $v;\n      \t\t\tnext if $configured->{$setting}++;\n      \t\t\t$$target = $v;\n    - \t\t}\n     @@ git-send-email.perl: sub config_regexp {\n      \tmy ($regex) = @_;\n      \tmy @ret;\n13:  d36b57e429f = 13:  aa3a2de7047 perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425751","messageId":"patch-01.13-81025b48f1c-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 01/13] send-email tests: support GIT_TEST_PERL_FATAL_WARNINGS=true","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:40Z","receivedAt":"2021-05-28T09:24:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add support for the \"GIT_TEST_PERL_FATAL_WARNINGS=true\" test mode to\n\"send-email\". This was added to e.g. git-svn in 5338ed2b26 (perl:\ncheck for perl warnings while running tests, 2020-10-21), but not\n\"send-email\". Let's rectify that.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 25be2ebd2af..1630e87cd4d 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -18,7 +18,7 @@\n \n use 5.008;\n use strict;\n-use warnings;\n+use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n use POSIX qw/strftime/;\n use Term::ReadLine;\n use Getopt::Long;\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425752","messageId":"patch-03.13-e3e3e6415d2-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 03/13] send-email: remove non-working support for \"sendemail.smtpssl\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:42Z","receivedAt":"2021-05-28T09:24:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove the already dead code to support \"sendemail.smtpssl\" by finally\nremoving the dead code supporting the configuration option.\n\nIn f6bebd121ac (git-send-email: add support for TLS via\nNet::SMTP::SSL, 2008-06-25) the --smtp-ssl command-line option was\ndocumented as deprecated, later in 65180c66186 (List send-email config\noptions in config.txt., 2009-07-22) the \"sendemail.smtpssl\"\nconfiguration option was also documented as such.\n\nThen in in 3ff15040e22 (send-email: fix regression in\nsendemail.identity parsing, 2019-05-17) I unintentionally removed\nsupport for it by introducing a bug in read_config().\n\nAs can be seen from the diff context we've already returned unless\n$enc i defined, so it's not possible for us to reach the \"elsif\"\nbranch here. This code was therefore already dead since Git v2.23.0.\n\nSo let's just remove it. We were already 11 years into a stated\ndeprecation period of this variable when 3ff15040e22 landed, now it's\naround 13. Since it hasn't worked anyway for around 2 years it looks\nlike we can safely remove it.\n\nThe --smtp-ssl option is still deprecated, if someone cares they can\nfollow-up and remove that too, but unlike the config option that one\ncould still be in use in the wild. I'm just removing this code that's\nprovably unused already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/config/sendemail.txt | 3 ---\n git-send-email.perl                | 6 +-----\n 2 files changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt\nindex cbc5af42fdf..50baa5d6bfb 100644\n--- a/Documentation/config/sendemail.txt\n+++ b/Documentation/config/sendemail.txt\n@@ -8,9 +8,6 @@ sendemail.smtpEncryption::\n \tSee linkgit:git-send-email[1] for description.  Note that this\n \tsetting is not subject to the 'identity' mechanism.\n \n-sendemail.smtpssl (deprecated)::\n-\tDeprecated alias for 'sendemail.smtpEncryption = ssl'.\n-\n sendemail.smtpsslcertpath::\n \tPath to ca-certificates (either a directory or a single file).\n \tSet it to an empty string to disable certificate verification.\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 1630e87cd4d..9a1a4898e36 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -393,11 +393,7 @@ sub read_config {\n \t\tmy $enc = Git::config(@repo, $setting);\n \t\treturn unless defined $enc;\n \t\treturn if $configured->{$setting}++;\n-\t\tif (defined $enc) {\n-\t\t\t$smtp_encryption = $enc;\n-\t\t} elsif (Git::config_bool(@repo, \"$prefix.smtpssl\")) {\n-\t\t\t$smtp_encryption = 'ssl';\n-\t\t}\n+\t\t$smtp_encryption = $enc;\n \t}\n }\n \n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425753","messageId":"patch-02.13-16277bd1082-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 02/13] send-email tests: test for boolean variables without a value","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:41Z","receivedAt":"2021-05-28T09:24:07Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The Git.pm code does its own Perl-ifying of boolean variables, let's\nensure that empty values = true for boolean variables, as in the C\ncode.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t9001-send-email.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 3b7540050ca..612de095fd7 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -1368,6 +1368,16 @@ test_expect_success $PREREQ 'sendemail.identity: bool variable fallback' '\n \t! grep \"X-Mailer\" stdout\n '\n \n+test_expect_success $PREREQ 'sendemail.identity: bool variable without a value' '\n+\tgit -c sendemail.xmailer \\\n+\t\tsend-email \\\n+\t\t--dry-run \\\n+\t\t--from=\"nobody@example.com\" \\\n+\t\t$patches >stdout &&\n+\tgrep \"To: default@example.com\" stdout &&\n+\tgrep \"X-Mailer\" stdout\n+'\n+\n test_expect_success $PREREQ '--no-to overrides sendemail.to' '\n \tgit send-email \\\n \t\t--dry-run \\\n@@ -2092,6 +2102,18 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=true' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer' '\n+\ttest_when_finished \"test_unconfig sendemail.xmailer\" &&\n+\tcat >>.git/config <<-\\EOF &&\n+\t[sendemail]\n+\t\txmailer\n+\tEOF\n+\ttest_config sendemail.xmailer true &&\n+\tdo_xmailer_test 1 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \ttest_config sendemail.xmailer false &&\n \tdo_xmailer_test 0 \"\" &&\n@@ -2099,6 +2121,13 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' '\n \tdo_xmailer_test 1 \"--xmailer\"\n '\n \n+test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=' '\n+\ttest_config sendemail.xmailer \"\" &&\n+\tdo_xmailer_test 0 \"\" &&\n+\tdo_xmailer_test 0 \"--no-xmailer\" &&\n+\tdo_xmailer_test 1 \"--xmailer\"\n+'\n+\n test_expect_success $PREREQ 'setup expected-list' '\n \tgit send-email \\\n \t--dry-run \\\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425754","messageId":"patch-04.13-961ca4c2b2a-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 04/13] send-email: refactor sendemail.smtpencryption config parsing","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:43Z","receivedAt":"2021-05-28T09:24:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"With the removal of the support for sendemail.smtpssl in the preceding\ncommit the parsing of sendemail.smtpencryption is no longer special,\nand can by moved to %config_settings.\n\nThis gets us rid of an unconditional call to Git::config(), which as\nwe'll see in subsequent commits matters for startup performance.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 9 +--------\n 1 file changed, 1 insertion(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 9a1a4898e36..e2fe112aa50 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -287,6 +287,7 @@ sub do_edit {\n );\n \n my %config_settings = (\n+    \"smtpencryption\" => \\$smtp_encryption,\n     \"smtpserver\" => \\$smtp_server,\n     \"smtpserverport\" => \\$smtp_server_port,\n     \"smtpserveroption\" => \\@smtp_server_options,\n@@ -387,14 +388,6 @@ sub read_config {\n \t\t\t$$target = $v;\n \t\t}\n \t}\n-\n-\tif (!defined $smtp_encryption) {\n-\t\tmy $setting = \"$prefix.smtpencryption\";\n-\t\tmy $enc = Git::config(@repo, $setting);\n-\t\treturn unless defined $enc;\n-\t\treturn if $configured->{$setting}++;\n-\t\t$smtp_encryption = $enc;\n-\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425755","messageId":"patch-06.13-4cf70c6f97e-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 06/13] send-email: lazily load config for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:45Z","receivedAt":"2021-05-28T09:24:10Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Reduce the time it takes git-send-email to get to even the most\ntrivial of tasks (such as serving up its \"-h\" output) by first listing\nconfig keys that exist, and only then only call e.g. \"git config\n--bool\" on them if they do.\n\nOver a lot of runs this speeds the time to \"-h\" up for me from ~250ms\nto ~150ms, and the runtime of t9001-send-email.sh goes from ~25s to\n~20s.\n\nThis introduces a race condition where we'll do the \"wrong\" thing if a\nconfig key were to be inserted between us discovering the list and\ncalling read_config(), i.e. we won't know about the racily added\nkey. In theory this is a change in behavior, in practice it doesn't\nmatter.\n\nThe config_regexp() function being changed here was added in\ndd84e528a34 (git-send-email: die if sendmail.* config is set,\n2020-07-23) for use by git-send-email. So we can change its odd return\nvalue in the case where no values are found by \"git config\". The\ndifference in the *.pm code would matter if it was invoked in scalar\ncontext, but now it no longer is.\n\nArguably this caching belongs in Git.pm itself, but in lieu of\nmodifying it for all its callers let's only do this for \"git\nsend-email\". The other big potential win would be \"git svn\", but\nunlike \"git send-email\" it doesn't check tens of config variables one\nat a time at startup (in my brief testing it doesn't check any).\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 35 ++++++++++++++++++++++++++---------\n 1 file changed, 26 insertions(+), 9 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 73e3d3fd26e..de62cbf2506 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -347,11 +347,13 @@ sub signal_handler {\n \n # Read our sendemail.* config\n sub read_config {\n-\tmy ($configured, $prefix) = @_;\n+\tmy ($known_keys, $configured, $prefix) = @_;\n \n \tforeach my $setting (keys %config_bool_settings) {\n \t\tmy $target = $config_bool_settings{$setting};\n-\t\tmy $v = Git::config_bool(@repo, \"$prefix.$setting\");\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n+\t\tmy $v = Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -359,8 +361,10 @@ sub read_config {\n \n \tforeach my $setting (keys %config_path_settings) {\n \t\tmy $target = $config_path_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config_path(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config_path(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n@@ -375,14 +379,16 @@ sub read_config {\n \n \tforeach my $setting (keys %config_settings) {\n \t\tmy $target = $config_settings{$setting};\n+\t\tmy $key = \"$prefix.$setting\";\n+\t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy @values = Git::config(@repo, $key);\n \t\t\tnext unless @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, \"$prefix.$setting\");\n+\t\t\tmy $v = Git::config(@repo, $key);\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -408,9 +414,20 @@ sub config_regexp {\n \treturn @ret;\n }\n \n+# Save ourselves a lot of work of shelling out to 'git config' (it\n+# parses 'bool' etc.) by only doing so for config keys that exist.\n+my %known_config_keys;\n+{\n+\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n+\t@known_config_keys{@known_config_keys} = ();\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n-$identity = Git::config(@repo, \"sendemail.identity\");\n+{\n+\tmy $key = \"sendemail.identity\";\n+\t$identity = Git::config(@repo, $key) if exists $known_config_keys{$key};\n+}\n my $rc = GetOptions(\n \t\"identity=s\" => \\$identity,\n \t\"no-identity\" => \\$no_identity,\n@@ -421,8 +438,8 @@ sub config_regexp {\n # Now we know enough to read the config\n {\n     my %configured;\n-    read_config(\\%configured, \"sendemail.$identity\") if defined $identity;\n-    read_config(\\%configured, \"sendemail\");\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail.$identity\") if defined $identity;\n+    read_config(\\%known_config_keys, \\%configured, \"sendemail\");\n }\n \n # Begin by accumulating all the variables (defined above), that we will end up\n@@ -506,7 +523,7 @@ sub config_regexp {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && grep { /^sendmail/s } keys %known_config_keys) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425756","messageId":"patch-05.13-f2bd12728a1-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 05/13] send-email: copy \"config_regxp\" into git-send-email.perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:44Z","receivedAt":"2021-05-28T09:24:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The config_regexp() function was added in dd84e528a3 (git-send-email:\ndie if sendmail.* config is set, 2020-07-23) for use in\ngit-send-email, and it's the only in-tree user of it.\n\nHowever, the consensus is that Git.pm is a public interface, so even\nthough it's a recently added function we can't change it. So let's\ncopy over a minimal version of it to git-send-email.perl itself. In a\nsubsequent commit it'll be changed further for our own use.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++++++-\n 1 file changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex e2fe112aa50..73e3d3fd26e 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -390,6 +390,24 @@ sub read_config {\n \t}\n }\n \n+sub config_regexp {\n+\tmy ($regex) = @_;\n+\tmy @ret;\n+\teval {\n+\t\t@ret = Git::command(\n+\t\t\t'config',\n+\t\t\t'--name-only',\n+\t\t\t'--get-regexp',\n+\t\t\t$regex,\n+\t\t);\n+\t\t1;\n+\t} or do {\n+\t\t# If we have no keys we're OK, otherwise re-throw\n+\t\tdie $@ if $@->value != 1;\n+\t};\n+\treturn @ret;\n+}\n+\n # sendemail.identity yields to --identity. We must parse this\n # special-case first before the rest of the config is read.\n $identity = Git::config(@repo, \"sendemail.identity\");\n@@ -488,7 +506,7 @@ sub read_config {\n     usage();\n }\n \n-if ($forbid_sendmail_variables && (scalar Git::config_regexp(\"^sendmail[.]\")) != 0) {\n+if ($forbid_sendmail_variables && (scalar config_regexp(\"^sendmail[.]\")) != 0) {\n \tdie __(\"fatal: found configuration options for 'sendmail'\\n\" .\n \t\t\"git-send-email is configured with the sendemail.* options - note the 'e'.\\n\" .\n \t\t\"Set sendemail.forbidSendmailVariables to false to disable this check.\\n\");\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425757","messageId":"patch-07.13-bd0d9535718-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 07/13] send-email: lazily shell out to \"git var\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:46Z","receivedAt":"2021-05-28T09:24:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize git-send-email by only shelling out to \"git var\" if we need\nto. This is easily done by re-inventing our own small version of\nperl's Memoize module.\n\nI suppose I could just use Memoize itself, but in a subsequent patch\nI'll be micro-optimizing send-email's use of dependencies. Using\nMemoize is a measly extra 5-10 milliseconds, but as we'll see that'll\nend up mattering for us in the end.\n\nThis brings the runtime of a plain \"send-email\" from around ~160-170ms\nto ~140m-150ms. The runtime of the tests is around the same, or around\n~20s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 20 +++++++++++++++-----\n 1 file changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex de62cbf2506..38408ec75a8 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -588,8 +588,18 @@ sub config_regexp {\n }\n \n my ($repoauthor, $repocommitter);\n-($repoauthor) = Git::ident_person(@repo, 'author');\n-($repocommitter) = Git::ident_person(@repo, 'committer');\n+{\n+\tmy %cache;\n+\tmy ($author, $committer);\n+\tmy $common = sub {\n+\t\tmy ($what) = @_;\n+\t\treturn $cache{$what} if exists $cache{$what};\n+\t\t($cache{$what}) = Git::ident_person(@repo, $what);\n+\t\treturn $cache{$what};\n+\t};\n+\t$repoauthor = sub { $common->('author') };\n+\t$repocommitter = sub { $common->('committer') };\n+}\n \n sub parse_address_line {\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n@@ -777,7 +787,7 @@ sub get_patch_subject {\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n \n-\tmy $tpl_sender = $sender || $repoauthor || $repocommitter || '';\n+\tmy $tpl_sender = $sender || $repoauthor->() || $repocommitter->() || '';\n \tmy $tpl_subject = $initial_subject || '';\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n@@ -983,7 +993,7 @@ sub file_declares_8bit_cte {\n \t$sender =~ s/^\\s+|\\s+$//g;\n \t($sender) = expand_aliases($sender);\n } else {\n-\t$sender = $repoauthor || $repocommitter || '';\n+\t$sender = $repoauthor->() || $repocommitter->() || '';\n }\n \n # $sender could be an already sanitized address\n@@ -1132,7 +1142,7 @@ sub make_message_id {\n \t$uniq = \"$message_id_stamp-$message_id_serial\";\n \n \tmy $du_part;\n-\tfor ($sender, $repocommitter, $repoauthor) {\n+\tfor ($sender, $repocommitter->(), $repoauthor->()) {\n \t\t$du_part = extract_valid_address(sanitize_address($_));\n \t\tlast if (defined $du_part and $du_part ne '');\n \t}\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425758","messageId":"patch-09.13-881b1093409-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 09/13] send-email: get rid of indirect object syntax","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:48Z","receivedAt":"2021-05-28T09:24:21Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change indirect object syntax such as \"new X ARGS\" to\n\"X->new(ARGS)\". This allows perl to see what \"new\" is at compile-time\nwithout having loaded Term::ReadLine. This doesn't matter now, but\nwill in a subsequent commit when we start lazily loading it.\n\nLet's do the same for the adjacent \"FakeTerm\" package for consistency,\neven though we're not going to conditionally load it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 44dc3f6eb10..cc1027d8774 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -194,11 +194,11 @@ sub format_2822_time {\n my @repo = $repo ? ($repo) : ();\n my $term = eval {\n \t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? new Term::ReadLine 'git-send-email', \\*STDIN, \\*STDOUT\n-\t\t: new Term::ReadLine 'git-send-email';\n+\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t: Term::ReadLine->new('git-send-email');\n };\n if ($@) {\n-\t$term = new FakeTerm \"$@: going non-interactive\";\n+\t$term = FakeTerm->new(\"$@: going non-interactive\");\n }\n \n # Behavior modification variables\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425759","messageId":"patch-08.13-f1a879a8ae9-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 08/13] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:47Z","receivedAt":"2021-05-28T09:24:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\ndoesn't have to guess that \"__\" is a function. This makes the code\nmore readable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 38408ec75a8..44dc3f6eb10 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -706,7 +706,7 @@ sub is_format_patch_arg {\n \t\tif (defined($format_patch)) {\n \t\t\treturn $format_patch;\n \t\t}\n-\t\tdie sprintf(__ <<EOF, $f, $f);\n+\t\tdie sprintf(__(<<EOF), $f, $f);\n File '%s' exists but it could also be the range of commits\n to produce patches for.  Please disambiguate by...\n \n@@ -792,7 +792,7 @@ sub get_patch_subject {\n \tmy $tpl_in_reply_to = $initial_in_reply_to || '';\n \tmy $tpl_reply_to = $reply_to || '';\n \n-\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __ <<EOT2), <<EOT3;\n+\tprint $c <<EOT1, Git::prefix_lines(\"GIT: \", __(<<EOT2)), <<EOT3;\n From $tpl_sender # This line is ignored.\n EOT1\n Lines beginning in \"GIT:\" will be removed.\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425760","messageId":"patch-10.13-9f21bc6e6f2-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 10/13] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:49Z","receivedAt":"2021-05-28T09:24:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the time git-send-email takes to do even the simplest of\nthings (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\nlazily loading the modules it requires.\n\nBefore this change Devel::TraceUse would report 99/97 used modules\nunder NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\nto run t9001-send-email.sh, down from ~20s.\n\nChanging File::Spec::Functions::{catdir,catfile} to invoking class\nmethods on File::Spec itself is idiomatic. See [1] for a more\nelaborate explanation, the resulting code behaves the same way, just\nwithout the now-pointless function wrapper.\n\n1. http://lore.kernel.org/git/8735u8mmj9.fsf@evledraar.gmail.com\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 71 +++++++++++++++++++++++++--------------------\n 1 file changed, 39 insertions(+), 32 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex cc1027d8774..a8949c9d313 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -19,20 +19,10 @@\n use 5.008;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n-use POSIX qw/strftime/;\n-use Term::ReadLine;\n use Getopt::Long;\n-use Text::ParseWords;\n-use Term::ANSIColor;\n-use File::Temp qw/ tempdir tempfile /;\n-use File::Spec::Functions qw(catdir catfile);\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n use Git;\n use Git::I18N;\n-use Net::Domain ();\n-use Net::SMTP ();\n-use Git::LoadCPAN::Mail::Address;\n \n Getopt::Long::Configure qw/ pass_through /;\n \n@@ -166,7 +156,6 @@ sub format_2822_time {\n \t\t       );\n }\n \n-my $have_email_valid = eval { require Email::Valid; 1 };\n my $smtp;\n my $auth;\n my $num_sent = 0;\n@@ -192,14 +181,6 @@ sub format_2822_time {\n \n my $repo = eval { Git->repository() };\n my @repo = $repo ? ($repo) : ();\n-my $term = eval {\n-\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n-\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n-\t\t: Term::ReadLine->new('git-send-email');\n-};\n-if ($@) {\n-\t$term = FakeTerm->new(\"$@: going non-interactive\");\n-}\n \n # Behavior modification variables\n my ($quiet, $dry_run) = (0, 0);\n@@ -319,9 +300,9 @@ sub do_edit {\n \n # Handle Uncouth Termination\n sub signal_handler {\n-\n \t# Make text normal\n-\tprint color(\"reset\"), \"\\n\";\n+\trequire Term::ANSIColor;\n+\tprint Term::ANSIColor::color(\"reset\"), \"\\n\";\n \n \t# SMTP password masked\n \tsystem \"stty echo\";\n@@ -602,11 +583,13 @@ sub config_regexp {\n }\n \n sub parse_address_line {\n+\trequire Git::LoadCPAN::Mail::Address;\n \treturn map { $_->format } Mail::Address->parse($_[0]);\n }\n \n sub split_addrs {\n-\treturn quotewords('\\s*,\\s*', 1, @_);\n+\trequire Text::ParseWords;\n+\treturn Text::ParseWords::quotewords('\\s*,\\s*', 1, @_);\n }\n \n my %aliases;\n@@ -655,10 +638,11 @@ sub parse_sendmail_aliases {\n \t\t\ts/\\\\\"/\"/g foreach @addr;\n \t\t\t$aliases{$alias} = \\@addr\n \t\t}}},\n-\tmailrc => sub { my $fh = shift; while (<$fh>) {\n+\tmailrc => sub {\tmy $fh = shift; while (<$fh>) {\n \t\tif (/^alias\\s+(\\S+)\\s+(.*?)\\s*$/) {\n+\t\t\trequire Text::ParseWords;\n \t\t\t# spaces delimit multiple addresses\n-\t\t\t$aliases{$1} = [ quotewords('\\s+', 0, $2) ];\n+\t\t\t$aliases{$1} = [ Text::ParseWords::quotewords('\\s+', 0, $2) ];\n \t\t}}},\n \tpine => sub { my $fh = shift; my $f='\\t[^\\t]*';\n \t        for (my $x = ''; defined($x); $x = $_) {\n@@ -730,7 +714,8 @@ sub is_format_patch_arg {\n \t\topendir my $dh, $f\n \t\t\tor die sprintf(__(\"Failed to opendir %s: %s\"), $f, $!);\n \n-\t\tpush @files, grep { -f $_ } map { catfile($f, $_) }\n+\t\trequire File::Spec;\n+\t\tpush @files, grep { -f $_ } map { File::Spec->catfile($f, $_) }\n \t\t\t\tsort readdir $dh;\n \t\tclosedir $dh;\n \t} elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) {\n@@ -743,7 +728,8 @@ sub is_format_patch_arg {\n if (@rev_list_opts) {\n \tdie __(\"Cannot run git format-patch from outside a repository\\n\")\n \t\tunless $repo;\n-\tpush @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);\n+\trequire File::Temp;\n+\tpush @files, $repo->command('format-patch', '-o', File::Temp::tempdir(CLEANUP => 1), @rev_list_opts);\n }\n \n @files = handle_backup_files(@files);\n@@ -780,9 +766,10 @@ sub get_patch_subject {\n if ($compose) {\n \t# Note that this does not need to be secure, but we will make a small\n \t# effort to have it be unique\n+\trequire File::Temp;\n \t$compose_filename = ($repo ?\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n-\t\ttempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => $repo->repo_path()) :\n+\t\tFile::Temp::tempfile(\".gitsendemail.msg.XXXXXX\", DIR => \".\"))[1];\n \topen my $c, \">\", $compose_filename\n \t\tor die sprintf(__(\"Failed to open for writing %s: %s\"), $compose_filename, $!);\n \n@@ -889,6 +876,19 @@ sub get_patch_subject {\n \tdo_edit(@files);\n }\n \n+sub term {\n+\tmy $term = eval {\n+\t\trequire Term::ReadLine;\n+\t\t$ENV{\"GIT_SEND_EMAIL_NOTTY\"}\n+\t\t\t? Term::ReadLine->new('git-send-email', \\*STDIN, \\*STDOUT)\n+\t\t\t: Term::ReadLine->new('git-send-email');\n+\t};\n+\tif ($@) {\n+\t\t$term = FakeTerm->new(\"$@: going non-interactive\");\n+\t}\n+\treturn $term;\n+}\n+\n sub ask {\n \tmy ($prompt, %arg) = @_;\n \tmy $valid_re = $arg{valid_re};\n@@ -896,6 +896,7 @@ sub ask {\n \tmy $confirm_only = $arg{confirm_only};\n \tmy $resp;\n \tmy $i = 0;\n+\tmy $term = term();\n \treturn defined $default ? $default : undef\n \t\tunless defined $term->IN and defined fileno($term->IN) and\n \t\t       defined $term->OUT and defined fileno($term->OUT);\n@@ -1076,6 +1077,7 @@ sub extract_valid_address {\n \treturn $address if ($address =~ /^($local_part_regexp)$/);\n \n \t$address =~ s/^\\s*<(.*)>\\s*$/$1/;\n+\tmy $have_email_valid = eval { require Email::Valid; 1 };\n \tif ($have_email_valid) {\n \t\treturn scalar Email::Valid->address($address);\n \t}\n@@ -1135,7 +1137,8 @@ sub validate_address_list {\n sub make_message_id {\n \tmy $uniq;\n \tif (!defined $message_id_stamp) {\n-\t\t$message_id_stamp = strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n+\t\trequire POSIX;\n+\t\t$message_id_stamp = POSIX::strftime(\"%Y%m%d%H%M%S.$$\", gmtime(time));\n \t\t$message_id_serial = 0;\n \t}\n \t$message_id_serial++;\n@@ -1305,6 +1308,7 @@ sub valid_fqdn {\n sub maildomain_net {\n \tmy $maildomain;\n \n+\trequire Net::Domain;\n \tmy $domain = Net::Domain::domainname();\n \t$maildomain = $domain if valid_fqdn($domain);\n \n@@ -1315,6 +1319,7 @@ sub maildomain_mta {\n \tmy $maildomain;\n \n \tfor my $host (qw(mailhost localhost)) {\n+\t\trequire Net::SMTP;\n \t\tmy $smtp = Net::SMTP->new($host);\n \t\tif (defined $smtp) {\n \t\t\tmy $domain = $smtp->domain;\n@@ -1994,13 +1999,15 @@ sub validate_patch {\n \n \tif ($repo) {\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n-\t\tmy $validate_hook = catfile($hooks_path,\n+\t\trequire File::Spec;\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n \t\t\t\t\t    'sendemail-validate');\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n-\t\t\tmy $target = abs_path($fn);\n+\t\t\trequire Cwd;\n+\t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = cwd();\n+\t\t\tmy $cwd_save = Cwd::cwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425761","messageId":"patch-12.13-f605b5ae49f-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 12/13] send-email: move trivial config handling to Perl","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:51Z","receivedAt":"2021-05-28T09:24:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Optimize the startup time of git-send-email by using an amended\nconfig_regexp() function to retrieve the list of config keys and\nvalues we're interested in.\n\nFor boolean keys we can handle the [true|false] case ourselves, and\nthe \"--get\" case didn't need any parsing. Let's leave \"--path\" and\nother \"--bool\" cases to \"git config\". I'm not bothering with the\n\"undef\" or \"\" case (true and false, respectively), let's just punt on\nthose and others and have \"git config --type=bool\" handle it.\n\nThe \"grep { defined } @values\" here covers a rather subtle case. For\nlist values such as sendemail.to it is possible as with any other\nconfig key to provide a plain \"-c sendemail.to\", i.e. to set the key\nas a boolean true. In that case the Git::config() API will return an\nempty string, but this new parser will correctly return \"undef\".\n\nHowever, that means we can end up with \"undef\" in the middle of a\nlist. E.g. for sendemail.smtpserveroption in conjuction with\nsendemail.smtpserver as a path this would have produce a warning. For\nmost of the other keys we'd behave the same despite the subtle change\nin the value, e.g. sendemail.to would behave the same because\nMail::Address->parse() happens to return an empty list if fed\n\"undef\". For the boolean values we were already prepared to handle\nthese variables being initialized as undef anyway.\n\nThis brings the runtime of \"git send-email\" from ~60-~70ms to a very\nsteady ~40ms on my test box. We now run just one \"git config\"\ninvocation on startup instead of 8, the exact number will differ based\non the local sendemail.* config. I happen to have 8 of those set.\n\nThis brings the runtime of t9001-send-email.sh from ~13s down to ~12s\nfor me. The change there is less impressive as many of those tests set\nvarious config values, and we're also getting to the point of\ndiminishing returns for optimizing \"git send-email\" itself.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 29 +++++++++++++++++++++--------\n 1 file changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex a8949c9d313..57911386835 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -334,7 +334,11 @@ sub read_config {\n \t\tmy $target = $config_bool_settings{$setting};\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n-\t\tmy $v = Git::config_bool(@repo, $key);\n+\t\tmy $v = (@{$known_keys->{$key}} == 1 &&\n+\t\t\t (defined $known_keys->{$key}->[0] &&\n+\t\t\t  $known_keys->{$key}->[0] =~ /^(?:true|false)$/s))\n+\t\t\t? $known_keys->{$key}->[0] eq 'true'\n+\t\t\t: Git::config_bool(@repo, $key);\n \t\tnext unless defined $v;\n \t\tnext if $configured->{$setting}++;\n \t\t$$target = $v;\n@@ -363,13 +367,13 @@ sub read_config {\n \t\tmy $key = \"$prefix.$setting\";\n \t\tnext unless exists $known_keys->{$key};\n \t\tif (ref($target) eq \"ARRAY\") {\n-\t\t\tmy @values = Git::config(@repo, $key);\n-\t\t\tnext unless @values;\n+\t\t\tmy @values = @{$known_keys->{$key}};\n+\t\t\t@values = grep { defined } @values;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t@$target = @values;\n \t\t}\n \t\telse {\n-\t\t\tmy $v = Git::config(@repo, $key);\n+\t\t\tmy $v = $known_keys->{$key}->[0];\n \t\t\tnext unless defined $v;\n \t\t\tnext if $configured->{$setting}++;\n \t\t\t$$target = $v;\n@@ -381,12 +385,19 @@ sub config_regexp {\n \tmy ($regex) = @_;\n \tmy @ret;\n \teval {\n-\t\t@ret = Git::command(\n+\t\tmy $ret = Git::command(\n \t\t\t'config',\n-\t\t\t'--name-only',\n+\t\t\t'--null',\n \t\t\t'--get-regexp',\n \t\t\t$regex,\n \t\t);\n+\t\t@ret = map {\n+\t\t\t# We must always return ($k, $v) here, since\n+\t\t\t# empty config values will be just \"key\\0\",\n+\t\t\t# not \"key\\nvalue\\0\".\n+\t\t\tmy ($k, $v) = split /\\n/, $_, 2;\n+\t\t\t($k, $v);\n+\t\t} split /\\0/, $ret;\n \t\t1;\n \t} or do {\n \t\t# If we have no keys we're OK, otherwise re-throw\n@@ -399,8 +410,10 @@ sub config_regexp {\n # parses 'bool' etc.) by only doing so for config keys that exist.\n my %known_config_keys;\n {\n-\tmy @known_config_keys = config_regexp(\"^sende?mail[.]\");\n-\t@known_config_keys{@known_config_keys} = ();\n+\tmy @kv = config_regexp(\"^sende?mail[.]\");\n+\twhile (my ($k, $v) = splice @kv, 0, 2) {\n+\t\tpush @{$known_config_keys{$k}} => $v;\n+\t}\n }\n \n # sendemail.identity yields to --identity. We must parse this\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425762","messageId":"patch-11.13-66f68e38c16-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 11/13] perl: lazily load some common Git.pm setup code","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:50Z","receivedAt":"2021-05-28T09:24:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Instead of unconditionally requiring modules such as File::Spec, let's\nonly load them when needed. This speeds up code that only needs a\nsubset of the features Git.pm provides.\n\nThis brings a plain invocation of \"git send-email\" down from 52/37\nloaded modules under NO_GETTEXT=[|Y] to 39/18, and it now takes\n~60-~70ms instead of ~80-~90ms. The runtime of t9001-send-email.sh\ntest is down to ~13s from ~15s.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n perl/Git.pm | 32 ++++++++++++++++----------------\n 1 file changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 02eacef0c2a..5562c0cede2 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -11,9 +11,6 @@ package Git;\n use strict;\n use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();\n \n-use File::Temp ();\n-use File::Spec ();\n-\n BEGIN {\n \n our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);\n@@ -103,12 +100,9 @@ =head1 DESCRIPTION\n =cut\n \n \n-use Carp qw(carp croak); # but croak is bad - throw instead\n+sub carp { require Carp; goto &Carp::carp }\n+sub croak { require Carp; goto &Carp::croak }\n use Git::LoadCPAN::Error qw(:try);\n-use Cwd qw(abs_path cwd);\n-use IPC::Open2 qw(open2);\n-use Fcntl qw(SEEK_SET SEEK_CUR);\n-use Time::Local qw(timegm);\n }\n \n \n@@ -191,13 +185,15 @@ sub repository {\n \t\t\t$dir = undef;\n \t\t};\n \n+\t\trequire Cwd;\n \t\tif ($dir) {\n+\t\t\trequire File::Spec;\n \t\t\tFile::Spec->file_name_is_absolute($dir) or $dir = $opts{Directory} . '/' . $dir;\n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \n \t\t\t# If --git-dir went ok, this shouldn't die either.\n \t\t\tmy $prefix = $search->command_oneline('rev-parse', '--show-prefix');\n-\t\t\t$dir = abs_path($opts{Directory}) . '/';\n+\t\t\t$dir = Cwd::abs_path($opts{Directory}) . '/';\n \t\t\tif ($prefix) {\n \t\t\t\tif (substr($dir, -length($prefix)) ne $prefix) {\n \t\t\t\t\tthrow Error::Simple(\"rev-parse confused me - $dir does not have trailing $prefix\");\n@@ -223,7 +219,7 @@ sub repository {\n \t\t\t\tthrow Error::Simple(\"fatal: Not a git repository: $dir\");\n \t\t\t}\n \n-\t\t\t$opts{Repository} = abs_path($dir);\n+\t\t\t$opts{Repository} = Cwd::abs_path($dir);\n \t\t}\n \n \t\tdelete $opts{Directory};\n@@ -408,10 +404,12 @@ sub command_bidi_pipe {\n \tmy $cwd_save = undef;\n \tif ($self) {\n \t\tshift;\n-\t\t$cwd_save = cwd();\n+\t\trequire Cwd;\n+\t\t$cwd_save = Cwd::cwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n-\t$pid = open2($in, $out, 'git', @_);\n+\trequire IPC::Open2;\n+\t$pid = IPC::Open2::open2($in, $out, 'git', @_);\n \tchdir($cwd_save) if $cwd_save;\n \treturn ($pid, $in, $out, join(' ', @_));\n }\n@@ -538,7 +536,8 @@ sub get_tz_offset {\n \tmy $t = shift || time;\n \tmy @t = localtime($t);\n \t$t[5] += 1900;\n-\tmy $gm = timegm(@t);\n+\trequire Time::Local;\n+\tmy $gm = Time::Local::timegm(@t);\n \tmy $sign = qw( + + - )[ $gm <=> $t ];\n \treturn sprintf(\"%s%02d%02d\", $sign, (gmtime(abs($t - $gm)))[2,1]);\n }\n@@ -1340,6 +1339,7 @@ sub _temp_cache {\n \t\tmy $n = $name;\n \t\t$n =~ s/\\W/_/g; # no strange chars\n \n+\t\trequire File::Temp;\n \t\t($$temp_fd, $fname) = File::Temp::tempfile(\n \t\t\t\"Git_${n}_XXXXXX\", UNLINK => 1, DIR => $tmpdir,\n \t\t\t) or throw Error::Simple(\"couldn't open new temp file\");\n@@ -1362,9 +1362,9 @@ sub temp_reset {\n \n \ttruncate $temp_fd, 0\n \t\tor throw Error::Simple(\"couldn't truncate file\");\n-\tsysseek($temp_fd, 0, SEEK_SET) and seek($temp_fd, 0, SEEK_SET)\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_SET()) and seek($temp_fd, 0, Fcntl::SEEK_SET())\n \t\tor throw Error::Simple(\"couldn't seek to beginning of file\");\n-\tsysseek($temp_fd, 0, SEEK_CUR) == 0 and tell($temp_fd) == 0\n+\tsysseek($temp_fd, 0, Fcntl::SEEK_CUR()) == 0 and tell($temp_fd) == 0\n \t\tor throw Error::Simple(\"expected file position to be reset\");\n }\n \n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425763","messageId":"patch-13.13-aa3a2de7047-20210528T092228Z-avarab@gmail.com","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"[PATCH v5 13/13] perl: nano-optimize by replacing Cwd::cwd() with Cwd::getcwd()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-28T09:23:52Z","receivedAt":"2021-05-28T09:24:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"It has been pointed out[1] that cwd() invokes \"pwd(1)\" while getcwd()\nis a Perl-native XS function. For what we're using these for we can\nuse getcwd().\n\nThe performance difference is miniscule, we're saving on the order of\na millisecond or so, see [2] below for the benchmark. I don't think\nthis matters in practice for optimizing git-send-email or perl\nexecution (unlike the patches leading up to this one).\n\nBut let's do it regardless of that, if only so we don't have to think\nabout this as a low-hanging fruit anymore.\n\n1. https://lore.kernel.org/git/20210512180517.GA11354@dcvr/\n2.\n    $ perl -MBenchmark=:all -MCwd -wE 'cmpthese(10000, { getcwd => sub { getcwd }, cwd => sub { cwd }, pwd => sub { system \"pwd >/dev/null\" }})'\n                (warning: too few iterations for a reliable count)\n                             Rate                  pwd                 cwd    getcwd\n    pwd                     982/s                   --                -48%     -100%\n    cwd                    1890/s                  92%                  --     -100%\n    getcwd 10000000000000000000/s 1018000000000000000% 529000000000000064%        -\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl | 2 +-\n perl/Git.pm         | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 57911386835..0efe85c0b02 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -2020,7 +2020,7 @@ sub validate_patch {\n \t\t\trequire Cwd;\n \t\t\tmy $target = Cwd::abs_path($fn);\n \t\t\t# The hook needs a correct cwd and GIT_DIR.\n-\t\t\tmy $cwd_save = Cwd::cwd();\n+\t\t\tmy $cwd_save = Cwd::getcwd();\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 5562c0cede2..090a7df63fc 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -405,7 +405,7 @@ sub command_bidi_pipe {\n \tif ($self) {\n \t\tshift;\n \t\trequire Cwd;\n-\t\t$cwd_save = Cwd::cwd();\n+\t\t$cwd_save = Cwd::getcwd();\n \t\t_setup_git_cmd_env($self);\n \t}\n \trequire IPC::Open2;\n-- \n2.32.0.rc1.458.gd885d4f985c\n\n"},{"id":"425806","messageId":"60b108608f0da_4b1112080@natae.notmuch","threadId":"55673","inReplyTo":"YKYdeom6SgAHqojm@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T15:12:32Z","receivedAt":"2021-05-28T15:12:39Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Jeff King wrote:\n> On Thu, May 20, 2021 at 10:18:57AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> > A re-roll of [1], the work I based v1 on top of has landed in master,\n> > so this has been rebased on master.\n> > \n> > The changes here are minor, just a typo fix / commit message\n> > clarification, moving \"require\" closer to where it's used, and finally\n> > a new 10/10 patch to s/cwd/getcwd/g.\n> \n> I like all of this, except for the change in the interface of\n> Git::config_regexp(). You mention that it's new-ish, and probably not in\n> wide use. And I agree that's probably true. But it feels like violating\n> a principle of not breaking APIs, and we should stick to that principle\n> and not bend it for \"well, it's not that old an API\".\n\nAPIs are broken all the time. That's why they have versions.\n\nAnyway, I find it odd that the git project cares about an API promise of\nan outdated language steadily in decline (it's peak was in 2004 [1]),\nand yet it does not provide an API of its own, like virtually all\nimportant software.\n\nShouldn't we care more about a C API than a Perl API?\n\n(a non-existent API is orders of magnitude worse than an API that broke\nonce)\n\n[1] https://www.tiobe.com/tiobe-index/perl/\n\n-- \nFelipe Contreras"},{"id":"425808","messageId":"60b11101e5288_50514208b4@natae.notmuch","threadId":"55673","inReplyTo":"875yzcpo52.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T15:49:21Z","receivedAt":"2021-05-28T15:49:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> Returning a flattened list is idiomatic in Perl, it means that a caller\n> can do any of:\n> \n>     # I only care about the last value for a key, or only about\n>     # existence checks\n>     my %hash = func();\n\nI was staying on the sideline because I don't know what's idiomatic in\nPerl, but Perl and Ruby share a lot in common (one could say Perl is the\ngrandfather of Ruby), and I do know very well what's idiomatic in Ruby.\n\nIn perl you can do $ENV{'USER'}, and:\n\n  while (my ($k, $v) = each %ENV) {\n    print \"$k = $v\\n\";\n  }\n\nObviously it's idiomatic to use hashes this way [1].\n\nIt was a waste for Git::config_regexp to not do the sensible thing here.\n\n\nYou can do exactly the same in Ruby: ENV['USER']\n\n  ENV.each { |k, v| print \"#{k} = #{v}\\n\" }\n\nAnd the way I would parse these configurations in Ruby is something like:\n\n  c = `git config -l -z`.split(\"\\0\").map { |e| e.split(\"\\n\") }.to_h\n  c['sendemail.smtpserver']\n\nAnd this just gave me an idea...\n\n[1] https://perldoc.perl.org/functions/each\n\n-- \nFelipe Contreras"},{"id":"425809","messageId":"60b112638fc47_4b03220885@natae.notmuch","threadId":"55673","inReplyTo":"patch-10.13-9f21bc6e6f2-20210528T092228Z-avarab@gmail.com","subject":"RE: [PATCH v5 10/13] send-email: lazily load modules for a big speedup","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T15:55:15Z","receivedAt":"2021-05-28T15:55:20Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> Optimize the time git-send-email takes to do even the simplest of\n> things (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\n> lazily loading the modules it requires.\n> \n> Before this change Devel::TraceUse would report 99/97 used modules\n> under NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\n> to run t9001-send-email.sh, down from ~20s.\n> \n> Changing File::Spec::Functions::{catdir,catfile} to invoking class\n> methods on File::Spec itself is idiomatic. See [1] for a more\n> elaborate explanation, the resulting code behaves the same way, just\n> without the now-pointless function wrapper.\n\nI would reference `man File::Spec` rather than an email.\n\nAnd while this change makes sense, I think it should be split in two.\n\nInstead of doing:\n\n  -use Term::ANSIColor;\n  -print color(\"reset\"), \"\\n\";\n  +require Term::ANSIColor;\n  +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n\nWe could do this in one patch:\n\n  -print color(\"reset\"), \"\\n\";\n  +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n\nThat is just no-op noise that we can mostly ignore in the review.\n\nThen the actual change to require Term::ANSIColor selectively would be\nmuch simpler to see.\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"425810","messageId":"60b116015b98a_4b03220857@natae.notmuch","threadId":"55673","inReplyTo":"patch-08.13-f1a879a8ae9-20210528T092228Z-avarab@gmail.com","subject":"RE: [PATCH v5 08/13] send-email: use function syntax instead of barewords","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T16:10:41Z","receivedAt":"2021-05-28T16:10:47Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\n> doesn't have to guess that \"__\" is a function. This makes the code\n> more readable.\n\nAgreed. Ruby has something similar:\n\n  foo arg1, arg2\n  foo(arg1, arg2)\n\nAs a C programmer I find the former a quirk of the language, and much\nharder to read.\n\nIf it's a function call, present it as such.\n\n-- \nFelipe Contreras"},{"id":"425812","messageId":"60b116b5c15b5_4b03220811@natae.notmuch","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"RE: [PATCH v5 00/13] send-email: various optimizations to speed up by >2x","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-28T16:13:41Z","receivedAt":"2021-05-28T16:13:46Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> Hopefully the final iteration. Updates a commit message to explain why\n> I moved away from File::Spec::Functions, rebases on master, and\n> explains and deals with the \"undef in config\" issue Jeff King noted.\n\nThe series looks good to me, except the one comment about splitting one\npatch, but not a deal-breaker.\n\nFor what it's worth:\nReviewed-by: Felipe Contreras <felipe.contreras@gmail.com>\n\n-- \nFelipe Contreras"},{"id":"425874","messageId":"87im322c04.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"60b112638fc47_4b03220885@natae.notmuch","subject":"Re: [PATCH v5 10/13] send-email: lazily load modules for a big speedup","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-29T08:12:53Z","receivedAt":"2021-05-29T08:13:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, May 28 2021, Felipe Contreras wrote:\n\n> Ævar Arnfjörð Bjarmason wrote:\n>> Optimize the time git-send-email takes to do even the simplest of\n>> things (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\n>> lazily loading the modules it requires.\n>> \n>> Before this change Devel::TraceUse would report 99/97 used modules\n>> under NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\n>> to run t9001-send-email.sh, down from ~20s.\n>> \n>> Changing File::Spec::Functions::{catdir,catfile} to invoking class\n>> methods on File::Spec itself is idiomatic. See [1] for a more\n>> elaborate explanation, the resulting code behaves the same way, just\n>> without the now-pointless function wrapper.\n>\n> I would reference `man File::Spec` rather than an email.\n>\n> And while this change makes sense, I think it should be split in two.\n>\n> Instead of doing:\n>\n>   -use Term::ANSIColor;\n>   -print color(\"reset\"), \"\\n\";\n>   +require Term::ANSIColor;\n>   +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n>\n> We could do this in one patch:\n>\n>   -print color(\"reset\"), \"\\n\";\n>   +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n>\n> That is just no-op noise that we can mostly ignore in the review.\n>\n> Then the actual change to require Term::ANSIColor selectively would be\n> much simpler to see.\n\nYou mean do the change from imported functions in one commit, and then\nsprinkle the \"require\" in another one?\n\nI think it's clearer this way, you can't really assert that it worked as\nintended (i.e. you have no more imports) without the s/use/require/g, so\nit makes sense to do both as one atomic change.\n"},{"id":"425875","messageId":"87fsy62bs1.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"60b116015b98a_4b03220857@natae.notmuch","subject":"Re: [PATCH v5 08/13] send-email: use function syntax instead of barewords","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-29T08:17:19Z","receivedAt":"2021-05-29T08:18:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, May 28 2021, Felipe Contreras wrote:\n\n> Ævar Arnfjörð Bjarmason wrote:\n>> Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\n>> doesn't have to guess that \"__\" is a function. This makes the code\n>> more readable.\n>\n> Agreed. Ruby has something similar:\n>\n>   foo arg1, arg2\n>   foo(arg1, arg2)\n>\n> As a C programmer I find the former a quirk of the language, and much\n> harder to read.\n>\n> If it's a function call, present it as such.\n\nI'm not too familiar with Ruby, but the important part here in Perl is\nthat it's not a \"foo()\" v.s. \"foo\" shortcut for a function invocation,\nit's unambiguous whether that is a function and what its arguments are\nuntil at that point in the program. Perl has dynamic grammar, you can't\nparse it with a regular grammar without executing it.\n"},{"id":"425876","messageId":"87czta2b47.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"60b11101e5288_50514208b4@natae.notmuch","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-29T08:19:07Z","receivedAt":"2021-05-29T08:34:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, May 28 2021, Felipe Contreras wrote:\n\n> Ævar Arnfjörð Bjarmason wrote:\n>> Returning a flattened list is idiomatic in Perl, it means that a caller\n>> can do any of:\n>> \n>>     # I only care about the last value for a key, or only about\n>>     # existence checks\n>>     my %hash = func();\n>\n> I was staying on the sideline because I don't know what's idiomatic in\n> Perl, but Perl and Ruby share a lot in common (one could say Perl is the\n> grandfather of Ruby), and I do know very well what's idiomatic in Ruby.\n>\n> In perl you can do $ENV{'USER'}, and:\n>\n>   while (my ($k, $v) = each %ENV) {\n>     print \"$k = $v\\n\";\n>   }\n>\n> Obviously it's idiomatic to use hashes this way [1].\n\nFor what it's worth idiomatic/good idea and \"has an example in the perl\ndocumentation\" unfortunately aren't always aligned. A lot of experienced\nPerl programmers avoid each() like the plague:\nhttp://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html\n\n> It was a waste for Git::config_regexp to not do the sensible thing here.\n\nFWIW we're commenting on a v2 of a series that's at v5 now, and doesn't\nuse config_regexp() at all, the relevant code is inlined in\ngit-send-email.perl now.\n\n> You can do exactly the same in Ruby: ENV['USER']\n>\n>   ENV.each { |k, v| print \"#{k} = #{v}\\n\" }\n>\n> And the way I would parse these configurations in Ruby is something like:\n\n>   c = `git config -l -z`.split(\"\\0\").map { |e| e.split(\"\\n\") }.to_h\n>   c['sendemail.smtpserver']\n>\n> And this just gave me an idea...\n\nI'd probably do it that way in Ruby, but not in Perl.\n\nThings that superficially look the same in two languages can have\ncompletely different behaviors, a \"hash\" isn't a single type of data\nstructure in these programming languages.\n\nIn particular Ruby doesn't have hshes in the Perl sense of the word, it\nhas an ordered key-value pair structure (IIRC under the hood they're\nhashes + a double linked list).\n\nThus you can use it for things like parsing a key=>value text file where\nthe key is unique and the order is important.\n\nIn Perl hashes are only meant for key-value lookup, they are not\nordered, and are actually actively randomly ordered for security\nreasons. In any modern version inserting a new key will have an avalance\neffect of completely changing the order. It's not even stable across\ninvocations:\n    \n    $ perl -wE 'my %h; for (\"a\"..\"z\") { $h{$_} = $_; say keys %h }'\n    a\n    ab\n    bca\n    dcba\n    daebc\n    cbaedf\n    aecbfdg\n    dgfcbaeh\n    [...]\n\nThe other important distinction (but I'm not so sure about Ruby here) is\nthat Perl doesn't have any way to pass a hash or any other structure to\nanother function, everything is flattened and pushed onto the stack.\n\nTo pass a \"hash\" you're not passing the hash, but a \"flattened\" pointer\nto it on the stack.\n\nThus passing and making use of these flattened values is idiomatic in\nPerl in a way that doesn't exist in a lot of other languages. In some\nother languages a function has to choose whether it's returning an array\nor a hash, in Perl you can just push the \"flattened\" items that make up\nthe array on the stack, and have the caller decide if they're pushing\nthose stack items into an array, or to a hash if they expect it to be\nmeaningful as key-value pairs.\n\nIn the context of Git's config format doing that is the perfect fit for\nconfig values, our config values *are* ordered, but they are also\nsort-of hashes, but whether it's \"all values\" or \"last value wins\" (or\nanything else, that's just the common ones) depends on the key/user.\n\nSo by having a list of key-value pairs on the stack you can choose to\nput it into an array if you don't want to lose information, or put it\ninto a hash if all you care about is \"last key wins\", or \"I'm going to\ncheck for key existence\".\n\nI think that in many other languages that wouldn't make any sense, and\nyou'd always return a structure like:\n\n    [\n         key => [zero or more values],\n        [...]\n    ]\n\nOr whatever, the caller can also unambiguously interpret those, but\nunlike Perl you'd need to write something to explicitly iterate the\nreturned value (or a helper) to get it into a hash or a \"flattened\"\narray. In Perl it's trivial due to the \"everything is on the stack\"\nsemantics.\n\nAnyway, all that being said the part we're talking about as a trivial\npart of this larger series. I'd much prefer to have it just land as\n\"good enough\" at this point. It works, we can always tweak it further\nlater if there's a need to do that.\n"},{"id":"425880","messageId":"60b24e84658ff_2650882081a@natae.notmuch","threadId":"55673","inReplyTo":"87im322c04.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 10/13] send-email: lazily load modules for a big speedup","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-29T14:24:04Z","receivedAt":"2021-05-29T14:24:10Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Fri, May 28 2021, Felipe Contreras wrote:\n> \n> > Ævar Arnfjörð Bjarmason wrote:\n> >> Optimize the time git-send-email takes to do even the simplest of\n> >> things (such as serving up \"-h\") from around ~150ms to ~80ms-~90ms by\n> >> lazily loading the modules it requires.\n> >> \n> >> Before this change Devel::TraceUse would report 99/97 used modules\n> >> under NO_GETTEXT=[|Y], respectively. Now it's 52/37. It now takes ~15s\n> >> to run t9001-send-email.sh, down from ~20s.\n> >> \n> >> Changing File::Spec::Functions::{catdir,catfile} to invoking class\n> >> methods on File::Spec itself is idiomatic. See [1] for a more\n> >> elaborate explanation, the resulting code behaves the same way, just\n> >> without the now-pointless function wrapper.\n> >\n> > I would reference `man File::Spec` rather than an email.\n> >\n> > And while this change makes sense, I think it should be split in two.\n> >\n> > Instead of doing:\n> >\n> >   -use Term::ANSIColor;\n> >   -print color(\"reset\"), \"\\n\";\n> >   +require Term::ANSIColor;\n> >   +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n> >\n> > We could do this in one patch:\n> >\n> >   -print color(\"reset\"), \"\\n\";\n> >   +print Term::ANSIColor::color(\"reset\"), \"\\n\";\n> >\n> > That is just no-op noise that we can mostly ignore in the review.\n> >\n> > Then the actual change to require Term::ANSIColor selectively would be\n> > much simpler to see.\n> \n> You mean do the change from imported functions in one commit, and then\n> sprinkle the \"require\" in another one?\n\nYes.\n \n> I think it's clearer this way, you can't really assert that it worked as\n> intended (i.e. you have no more imports) without the s/use/require/g, so\n> it makes sense to do both as one atomic change.\n\nIt is clear, but there's some noise.\n\nAnd I don't see how you can assert there are no more imports from this\npatch, especially when the next patch gets rid of more imports.\n\nMoreover, it's already the case that the code uses namespaces in some\ncases: Net::Domain::domainname is one you did not have to convert. You\nwould be doing the same for all other instances.\n\nCheers.\n\n\n-- \nFelipe Contreras"},{"id":"425881","messageId":"60b24ec34b065_2650882086e@natae.notmuch","threadId":"55673","inReplyTo":"87fsy62bs1.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 08/13] send-email: use function syntax instead of barewords","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-29T14:25:07Z","receivedAt":"2021-05-29T14:25:13Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Fri, May 28 2021, Felipe Contreras wrote:\n> \n> > Ævar Arnfjörð Bjarmason wrote:\n> >> Change calls like \"__ 'foo'\" to \"__('foo')\" so the Perl compiler\n> >> doesn't have to guess that \"__\" is a function. This makes the code\n> >> more readable.\n> >\n> > Agreed. Ruby has something similar:\n> >\n> >   foo arg1, arg2\n> >   foo(arg1, arg2)\n> >\n> > As a C programmer I find the former a quirk of the language, and much\n> > harder to read.\n> >\n> > If it's a function call, present it as such.\n> \n> I'm not too familiar with Ruby, but the important part here in Perl is\n> that it's not a \"foo()\" v.s. \"foo\" shortcut for a function invocation,\n> it's unambiguous whether that is a function and what its arguments are\n> until at that point in the program. Perl has dynamic grammar, you can't\n> parse it with a regular grammar without executing it.\n\nYes, that is the important part, but not the only part.\n\nI'm saying in addition it's easier to read for most git.git developers.\n\n-- \nFelipe Contreras"},{"id":"425882","messageId":"60b2557b5c7da_2650882084e@natae.notmuch","threadId":"55673","inReplyTo":"87czta2b47.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-29T14:53:47Z","receivedAt":"2021-05-29T14:53:53Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Fri, May 28 2021, Felipe Contreras wrote:\n> \n> > Ævar Arnfjörð Bjarmason wrote:\n> >> Returning a flattened list is idiomatic in Perl, it means that a caller\n> >> can do any of:\n> >> \n> >>     # I only care about the last value for a key, or only about\n> >>     # existence checks\n> >>     my %hash = func();\n> >\n> > I was staying on the sideline because I don't know what's idiomatic in\n> > Perl, but Perl and Ruby share a lot in common (one could say Perl is the\n> > grandfather of Ruby), and I do know very well what's idiomatic in Ruby.\n> >\n> > In perl you can do $ENV{'USER'}, and:\n> >\n> >   while (my ($k, $v) = each %ENV) {\n> >     print \"$k = $v\\n\";\n> >   }\n> >\n> > Obviously it's idiomatic to use hashes this way [1].\n> \n> For what it's worth idiomatic/good idea and \"has an example in the perl\n> documentation\" unfortunately aren't always aligned. A lot of experienced\n> Perl programmers avoid each() like the plague:\n> http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html\n\nPerl is an old language, and each() was introduced in 2010, it's\nexpected that some old-timers would not adapt to the new idioms.\n\nBTW, Ruby borrowed a lot from Perl, but I'm pretty sure Perl borrowed\neach() from Ruby.\n\nUntilmately it doesn't matter what you use to traverse %ENV, my point is\nthat it's a hash.\n\n> > It was a waste for Git::config_regexp to not do the sensible thing here.\n> \n> FWIW we're commenting on a v2 of a series that's at v5 now, and doesn't\n> use config_regexp() at all, the relevant code is inlined in\n> git-send-email.perl now.\n\nI know, I've been following the threads. I'm trying to say it's a shame\nGit::config_regexp does not do the sensible thing.\n\n> > You can do exactly the same in Ruby: ENV['USER']\n> >\n> >   ENV.each { |k, v| print \"#{k} = #{v}\\n\" }\n> >\n> > And the way I would parse these configurations in Ruby is something like:\n> \n> >   c = `git config -l -z`.split(\"\\0\").map { |e| e.split(\"\\n\") }.to_h\n> >   c['sendemail.smtpserver']\n> >\n> > And this just gave me an idea...\n> \n> I'd probably do it that way in Ruby, but not in Perl.\n> \n> Things that superficially look the same in two languages can have\n> completely different behaviors, a \"hash\" isn't a single type of data\n> structure in these programming languages.\n> \n> In particular Ruby doesn't have hshes in the Perl sense of the word, it\n> has an ordered key-value pair structure (IIRC under the hood they're\n> hashes + a double linked list).\n> \n> Thus you can use it for things like parsing a key=>value text file where\n> the key is unique and the order is important.\n> \n> In Perl hashes are only meant for key-value lookup, they are not\n> ordered, and are actually actively randomly ordered for security\n> reasons. In any modern version inserting a new key will have an avalance\n> effect of completely changing the order. It's not even stable across\n> invocations:\n>     \n>     $ perl -wE 'my %h; for (\"a\"..\"z\") { $h{$_} = $_; say keys %h }'\n>     a\n>     ab\n>     bca\n>     dcba\n>     daebc\n>     cbaedf\n>     aecbfdg\n>     dgfcbaeh\n>     [...]\n\nThis used to be the case in Ruby too. The order of hashes was not\nguaranteed.\n\nThe situation is more complicated because not only do you have different\nversions, but you have different implementations. AFAIK the Ruby\nlanguage specification doesn't say anything about ordering, although\nbasically all implementations do order.\n\n> The other important distinction (but I'm not so sure about Ruby here) is\n> that Perl doesn't have any way to pass a hash or any other structure to\n> another function, everything is flattened and pushed onto the stack.\n> \n> To pass a \"hash\" you're not passing the hash, but a \"flattened\" pointer\n> to it on the stack.\n> \n> Thus passing and making use of these flattened values is idiomatic in\n> Perl in a way that doesn't exist in a lot of other languages. In some\n> other languages a function has to choose whether it's returning an array\n> or a hash, in Perl you can just push the \"flattened\" items that make up\n> the array on the stack, and have the caller decide if they're pushing\n> those stack items into an array, or to a hash if they expect it to be\n> meaningful as key-value pairs.\n\nYeah, that's something that wasn't borrowed. In Ruby everything is an\nobject.\n\n> In the context of Git's config format doing that is the perfect fit for\n> config values, our config values *are* ordered, but they are also\n> sort-of hashes, but whether it's \"all values\" or \"last value wins\" (or\n> anything else, that's just the common ones) depends on the key/user.\n> \n> So by having a list of key-value pairs on the stack you can choose to\n> put it into an array if you don't want to lose information, or put it\n> into a hash if all you care about is \"last key wins\", or \"I'm going to\n> check for key existence\".\n> \n> I think that in many other languages that wouldn't make any sense, and\n> you'd always return a structure like:\n> \n>     [\n>          key => [zero or more values],\n>         [...]\n>     ]\n> \n> Or whatever, the caller can also unambiguously interpret those, but\n> unlike Perl you'd need to write something to explicitly iterate the\n> returned value (or a helper) to get it into a hash or a \"flattened\"\n> array. In Perl it's trivial due to the \"everything is on the stack\"\n> semantics.\n\nIndeed, but my point is that it's a hash for all intents and purposes:\n\n  my %hash = func();\n\nAnd it makes sense for it to be a hash, just like %ENV.\n\nAnd although the internals are different something very close would\nhappen in Ruby:\n\n  hash = func().to_h\n\n> Anyway, all that being said the part we're talking about as a trivial\n> part of this larger series. I'd much prefer to have it just land as\n> \"good enough\" at this point. It works, we can always tweak it further\n> later if there's a need to do that.\n\nIndeed, as I said, the entire patch series looks good to me.\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"425914","messageId":"877djg313c.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"60b2557b5c7da_2650882084e@natae.notmuch","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-30T11:33:31Z","receivedAt":"2021-05-30T11:36:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, May 29 2021, Felipe Contreras wrote:\n\n> Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Fri, May 28 2021, Felipe Contreras wrote:\n>> \n>> > Ævar Arnfjörð Bjarmason wrote:\n>> >> Returning a flattened list is idiomatic in Perl, it means that a caller\n>> >> can do any of:\n>> >> \n>> >>     # I only care about the last value for a key, or only about\n>> >>     # existence checks\n>> >>     my %hash = func();\n>> >\n>> > I was staying on the sideline because I don't know what's idiomatic in\n>> > Perl, but Perl and Ruby share a lot in common (one could say Perl is the\n>> > grandfather of Ruby), and I do know very well what's idiomatic in Ruby.\n>> >\n>> > In perl you can do $ENV{'USER'}, and:\n>> >\n>> >   while (my ($k, $v) = each %ENV) {\n>> >     print \"$k = $v\\n\";\n>> >   }\n>> >\n>> > Obviously it's idiomatic to use hashes this way [1].\n>> \n>> For what it's worth idiomatic/good idea and \"has an example in the perl\n>> documentation\" unfortunately aren't always aligned. A lot of experienced\n>> Perl programmers avoid each() like the plague:\n>> http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html\n>\n> Perl is an old language, and each() was introduced in 2010, it's\n> expected that some old-timers would not adapt to the new idioms.\n\neach() has been in Perl since 1987 with perl v1.0, you must be confusing\nit with something else.\n\nIn any case, the recommendation against it has nothing to do with its\nage, it's that similar to strtok() it has global state. So e.g. you\ncan't safely iterate over a hash you're modifying with it, or if you\niterate over a top-level structure and pass that variable down to\nanother function, and it also uses each()...\n\n"},{"id":"425926","messageId":"60b3b84967a62_b937208c@natae.notmuch","threadId":"55673","inReplyTo":"877djg313c.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/10] send-email: various optimizations to speed up by >2x","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-05-30T16:07:37Z","receivedAt":"2021-05-30T16:07:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Sat, May 29 2021, Felipe Contreras wrote:\n> \n> > Ævar Arnfjörð Bjarmason wrote:\n> >> \n> >> On Fri, May 28 2021, Felipe Contreras wrote:\n> >> \n> >> > Ævar Arnfjörð Bjarmason wrote:\n> >> >> Returning a flattened list is idiomatic in Perl, it means that a caller\n> >> >> can do any of:\n> >> >> \n> >> >>     # I only care about the last value for a key, or only about\n> >> >>     # existence checks\n> >> >>     my %hash = func();\n> >> >\n> >> > I was staying on the sideline because I don't know what's idiomatic in\n> >> > Perl, but Perl and Ruby share a lot in common (one could say Perl is the\n> >> > grandfather of Ruby), and I do know very well what's idiomatic in Ruby.\n> >> >\n> >> > In perl you can do $ENV{'USER'}, and:\n> >> >\n> >> >   while (my ($k, $v) = each %ENV) {\n> >> >     print \"$k = $v\\n\";\n> >> >   }\n> >> >\n> >> > Obviously it's idiomatic to use hashes this way [1].\n> >> \n> >> For what it's worth idiomatic/good idea and \"has an example in the perl\n> >> documentation\" unfortunately aren't always aligned. A lot of experienced\n> >> Perl programmers avoid each() like the plague:\n> >> http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html\n> >\n> > Perl is an old language, and each() was introduced in 2010, it's\n> > expected that some old-timers would not adapt to the new idioms.\n> \n> each() has been in Perl since 1987 with perl v1.0, you must be confusing\n> it with something else.\n\nI see. I read the each() documentation [1] too hastily:\n\n  When called on a hash in list context, returns a 2-element list\n  consisting of the key and value for the next element of a hash. In\n  Perl 5.12 and later only...\n\n> In any case, the recommendation against it has nothing to do with its\n> age, it's that similar to strtok() it has global state.\n\nYes, that's what I understood from the blog post you shared, but at\nleast personally I never assume I can modify a hash like that. I see why\nsome people need to be careful with it, but \"avoid it like the plague\"\nseems way too defensive programming to me.\n\n[1] https://perldoc.perl.org/functions/each\n\n-- \nFelipe Contreras"},{"id":"425968","messageId":"YLR4tMOvkR81+ACq@coredump.intra.peff.net","threadId":"55673","inReplyTo":"cover-00.13-00000000000-20210528T092228Z-avarab@gmail.com","subject":"Re: [PATCH v5 00/13] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-31T05:48:36Z","receivedAt":"2021-05-31T05:48:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 28, 2021 at 11:23:39AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> Hopefully the final iteration. Updates a commit message to explain why\n> I moved away from File::Spec::Functions, rebases on master, and\n> explains and deals with the \"undef in config\" issue Jeff King noted.\n\nThanks. The solution was less invasive than I feared.\n\nI guess here:\n\n>     @@ git-send-email.perl: sub read_config {\n>      -\t\t\tmy @values = Git::config(@repo, $key);\n>      -\t\t\tnext unless @values;\n>      +\t\t\tmy @values = @{$known_keys->{$key}};\n>     ++\t\t\t@values = grep { defined } @values;\n>       \t\t\tnext if $configured->{$setting}++;\n>       \t\t\t@$target = @values;\n>       \t\t}\n>       \t\telse {\n>      -\t\t\tmy $v = Git::config(@repo, $key);\n>     --\t\t\tnext unless defined $v;\n>      +\t\t\tmy $v = $known_keys->{$key}->[0];\n>     + \t\t\tnext unless defined $v;\n>       \t\t\tnext if $configured->{$setting}++;\n>       \t\t\t$$target = $v;\n>     - \t\t}\n\nwe'd ignore such undef values, whereas presumably before they'd have\ntriggered an error via Git::config(). I don't think it matters all that\nmuch either way, though.\n\n-Peff\n"},{"id":"425993","messageId":"874kej2pjw.fsf@evledraar.gmail.com","threadId":"55673","inReplyTo":"YLR4tMOvkR81+ACq@coredump.intra.peff.net","subject":"Re: [PATCH v5 00/13] send-email: various optimizations to speed up by >2x","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-31T09:53:04Z","receivedAt":"2021-05-31T09:59:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, May 31 2021, Jeff King wrote:\n\n> On Fri, May 28, 2021 at 11:23:39AM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n>> Hopefully the final iteration. Updates a commit message to explain why\n>> I moved away from File::Spec::Functions, rebases on master, and\n>> explains and deals with the \"undef in config\" issue Jeff King noted.\n>\n> Thanks. The solution was less invasive than I feared.\n>\n> I guess here:\n>\n>>     @@ git-send-email.perl: sub read_config {\n>>      -\t\t\tmy @values = Git::config(@repo, $key);\n>>      -\t\t\tnext unless @values;\n>>      +\t\t\tmy @values = @{$known_keys->{$key}};\n>>     ++\t\t\t@values = grep { defined } @values;\n>>       \t\t\tnext if $configured->{$setting}++;\n>>       \t\t\t@$target = @values;\n>>       \t\t}\n>>       \t\telse {\n>>      -\t\t\tmy $v = Git::config(@repo, $key);\n>>     --\t\t\tnext unless defined $v;\n>>      +\t\t\tmy $v = $known_keys->{$key}->[0];\n>>     + \t\t\tnext unless defined $v;\n>>       \t\t\tnext if $configured->{$setting}++;\n>>       \t\t\t$$target = $v;\n>>     - \t\t}\n>\n> we'd ignore such undef values, whereas presumably before they'd have\n> triggered an error via Git::config(). I don't think it matters all that\n> much either way, though.\n\nThey didn't error before, they were treated the same as the empty\nstring.\n\nThis is because Git.pm uses \"git config --get\" to retrieve them, but we\nnow use \"--list --null\".\n\nThis ultimately comes down to a limitation of git-config's one-shot CLI\ninterface. You get the empty string and zero exit code for both of:\n\n    git -c foo.bar= config --get foo.bar\n    git -c foo.bar config --get foo.bar\n\nIgnoring them here for list values is technically a behavior change if\nwe were treating this as a black box, but in reality we know that these\ncould only conceivable be useful for the bool values, for list values\nlike \"list of e-mail addresses\" it makes no sense to have that in a the\nmiddle of a list, and we were implicitly ignoring them anyway before\nwhen we processed the empty string.\n"},{"id":"426001","messageId":"YLT0znOFZGd/zmDa@coredump.intra.peff.net","threadId":"55673","inReplyTo":"874kej2pjw.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 00/13] send-email: various optimizations to speed up by >2x","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-05-31T14:38:06Z","receivedAt":"2021-05-31T14:40:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, May 31, 2021 at 11:53:04AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> > I guess here:\n> >\n> >>     @@ git-send-email.perl: sub read_config {\n> >>      -\t\t\tmy @values = Git::config(@repo, $key);\n> >>      -\t\t\tnext unless @values;\n> >>      +\t\t\tmy @values = @{$known_keys->{$key}};\n> >>     ++\t\t\t@values = grep { defined } @values;\n> >>       \t\t\tnext if $configured->{$setting}++;\n> >>       \t\t\t@$target = @values;\n> >>       \t\t}\n> >>       \t\telse {\n> >>      -\t\t\tmy $v = Git::config(@repo, $key);\n> >>     --\t\t\tnext unless defined $v;\n> >>      +\t\t\tmy $v = $known_keys->{$key}->[0];\n> >>     + \t\t\tnext unless defined $v;\n> >>       \t\t\tnext if $configured->{$setting}++;\n> >>       \t\t\t$$target = $v;\n> >>     - \t\t}\n> >\n> > we'd ignore such undef values, whereas presumably before they'd have\n> > triggered an error via Git::config(). I don't think it matters all that\n> > much either way, though.\n> \n> They didn't error before, they were treated the same as the empty\n> string.\n> \n> This is because Git.pm uses \"git config --get\" to retrieve them, but we\n> now use \"--list --null\".\n> \n> This ultimately comes down to a limitation of git-config's one-shot CLI\n> interface. You get the empty string and zero exit code for both of:\n> \n>     git -c foo.bar= config --get foo.bar\n>     git -c foo.bar config --get foo.bar\n\nAh, OK. Thanks for clarifying.\n\n> Ignoring them here for list values is technically a behavior change if\n> we were treating this as a black box, but in reality we know that these\n> could only conceivable be useful for the bool values, for list values\n> like \"list of e-mail addresses\" it makes no sense to have that in a the\n> middle of a list, and we were implicitly ignoring them anyway before\n> when we processed the empty string.\n\nRight, it was the list one I was most puzzled by. But if we were already\nconflating implicit-bool and the empty string, I think it's OK.  I agree\nthat all of this is something that _shouldn't_ happen in the first\nplace, for well-formed config.\n\n-Peff\n"},{"id":"429650","messageId":"YOor1ztKLGvfk68i@coredump.intra.peff.net","threadId":"55673","inReplyTo":"patch-02.13-16277bd1082-20210528T092228Z-avarab@gmail.com","subject":"Re: [PATCH v5 02/13] send-email tests: test for boolean variables without a value","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-07-10T23:23:03Z","receivedAt":"2021-07-10T23:23:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, May 28, 2021 at 11:23:41AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> +test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer' '\n> +\ttest_when_finished \"test_unconfig sendemail.xmailer\" &&\n> +\tcat >>.git/config <<-\\EOF &&\n> +\t[sendemail]\n> +\t\txmailer\n> +\tEOF\n> +\ttest_config sendemail.xmailer true &&\n> +\tdo_xmailer_test 1 \"\" &&\n> +\tdo_xmailer_test 0 \"--no-xmailer\" &&\n> +\tdo_xmailer_test 1 \"--xmailer\"\n> +'\n\nIs this test_config call in the middle of the snippet meant to be there?\nIt seems like it would wreck the test, since it will rewrite the file to\n\"xmailer = true\". I'm guessing it's just leftover cruft from adapting\nthe earlier xmailer=true test?\n\n(I wasn't looking carefully again at this series, but just happened to\nbe resolving a local conflict involving this hunk and noticed it).\n\n-Peff\n"}]}