{"thread":{"id":"66592","subject":"[PATCH 0/2] Fix imap-send with reordered message headers","startedAt":"2026-10-19T19:19:16Z","lastAt":"2026-11-01T08:21:37Z","messageCount":39,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"555447","messageId":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":null,"subject":"[PATCH 0/2] Fix imap-send with reordered message headers","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-19T19:19:16Z","receivedAt":"2026-10-19T19:19:16Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"git imap-send rejected patches sent with git send-email, such as ones\nfetched from lore with b4, because it expected Date before Subject.\n\nHarald Nordgren (2):\n  imap-send: avoid leaking the tunnel command\n  imap-send: accept messages with Subject before Date\n\n imap-send.c           | 38 ++++++++++++++++++--------------------\n t/t9001-send-email.sh | 35 +++++++++++++++++++++++++++++++++++\n 2 files changed, 53 insertions(+), 20 deletions(-)\n\n\nbase-commit: c51408889c3b372814bc20b9431c3b74d0d268eb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2456%2FHaraldNordgren%2Ffix%2Fimap-send-header-order-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2456/HaraldNordgren/fix/imap-send-header-order-v1\nPull-Request: https://github.com/git/git/pull/2456\n-- \ngitgitgadget\n\n"},{"id":"555448","messageId":"0784c16134e4b2f9c969e46ffac241e1901b264f.1792437558.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH 1/2] imap-send: avoid leaking the tunnel command","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-19T19:19:17Z","receivedAt":"2026-10-19T19:19:17Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen imap.tunnel is set, imap_open_store() starts the tunnel but never\nreleases the child_process used for it, which leaks its argument list.\nNo test sets imap.tunnel, so the leak checker has not caught this.\n\nOnly the pipes to the tunnel are needed once it is running, so release\nthe rest right after starting it.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bf1d2cf74d..6f76bade35 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1176,6 +1176,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n+\t\tchild_process_clear(&tunnel);\n \n \t\timap_info(\"OK\\n\");\n \t} else {\n-- \ngitgitgadget\n\n\n"},{"id":"555449","messageId":"94129fb718c0fd961df78550f6633752d474f825.1792437558.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH 2/2] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-19T19:19:18Z","receivedAt":"2026-10-19T19:19:18Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nPatches sent with git send-email cannot be fed back into git imap-send\nafter fetching them from the mailing list archive, for example with\n\n    b4 mbox --single-message -o - <url> | git imap-send\n\nwhich fails with \"no messages found to send\". imap-send only counts a\nmessage when its From, Date and Subject headers come in that order,\nand git send-email writes Subject before Date.\n\nAccept the three headers in any order within the header block of each\nmessage. The copy that git send-email --imap-sent-folder pipes to\nimap-send then passes the same check, so the special case added for it\nin 04133f5bc4 (send-email: add ability to send a copy of sent emails to\nan IMAP folder, 2025-08-12) is no longer needed.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 37 +++++++++++++++++--------------------\n t/t9001-send-email.sh | 35 +++++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 20 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 6f76bade35..6563c8f693 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1453,32 +1453,29 @@ static void wrap_in_html(struct strbuf *msg)\n \t*msg = buf;\n }\n \n+static bool has_required_headers(const char *line)\n+{\n+\tbool from = false, date = false, subject = false;\n+\n+\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n+\t\tif (starts_with(line, \"From: \"))\n+\t\t\tfrom = true;\n+\t\telse if (starts_with(line, \"Date: \"))\n+\t\t\tdate = true;\n+\t\telse if (starts_with(line, \"Subject: \"))\n+\t\t\tsubject = true;\n+\t}\n+\treturn from && date && subject;\n+}\n+\n static int count_messages(struct strbuf *all_msgs)\n {\n \tint count = 0;\n \tchar *p = all_msgs->buf;\n \n \twhile (1) {\n-\t\tif (starts_with(p, \"From \")) {\n-\t\t\tif (starts_with(p, \"From git-send-email\")) {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 7;\n-\t\t\t\tp = strstr(p, \"\\nTo: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 5;\n-\t\t\t\tcount++;\n-\t\t\t} else {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 10;\n-\t\t\t\tcount++;\n-\t\t\t}\n-\t\t}\n+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n+\t\t\tcount++;\n \t\tp = strstr(p+5, \"\\nFrom \");\n \t\tif (!p)\n \t\t\tbreak;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d1393ef197..1f77c14315 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n \ttest_cmp expect msgtxt2.to\n '\n \n+test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n+\twrite_script fake-imap-server <<-\\EOF &&\n+\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n+\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n+\tcat >/dev/null\n+\tEOF\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\tHEAD^ &&\n+\t{\n+\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n+\t\tcat msgtxt1\n+\t} >archive.mbox &&\n+\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\t--imap-sent-folder=Sent \\\n+\t\tHEAD^ 2>err &&\n+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"555476","messageId":"xmqqse211hck.fsf@gitster.g","threadId":"66592","inReplyTo":"94129fb718c0fd961df78550f6633752d474f825.1792437558.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] imap-send: accept messages with Subject before Date","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-19T22:00:11Z","receivedAt":"2026-10-19T22:00:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> which fails with \"no messages found to send\". imap-send only counts a\n> message when its From, Date and Subject headers come in that order,\n> and git send-email writes Subject before Date.\n\nNicely analysed.\n\n> +static bool has_required_headers(const char *line)\n> +{\n> +\tbool from = false, date = false, subject = false;\n> +\n> +\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n> +\t\tif (starts_with(line, \"From: \"))\n> +\t\t\tfrom = true;\n> +\t\telse if (starts_with(line, \"Date: \"))\n> +\t\t\tdate = true;\n> +\t\telse if (starts_with(line, \"Subject: \"))\n> +\t\t\tsubject = true;\n> +\t}\n> +\treturn from && date && subject;\n> +}\n\nSo the caller is responsible for placing the cursor at the beginning\nof a message (e.g., a \"From \" line) and this one inspects the lines\nstarting at the point looking for the headers we want to see.  The\ncursor is local so our incrementing will not break the caller.\n\nRFC 822/2822/5322 specifies these headers as case insensitive, so\nyou'd want to use istarts_with() instead, though.  I can see that\nthe original did not get it right, but we are making the wrongs in\nthe original right, so ...\n\n>  static int count_messages(struct strbuf *all_msgs)\n>  {\n>  \tint count = 0;\n>  \tchar *p = all_msgs->buf;\n>  \n>  \twhile (1) {\n> -\t\tif (starts_with(p, \"From \")) {\n> -\t\t\tif (starts_with(p, \"From git-send-email\")) {\n> -\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 7;\n> -\t\t\t\tp = strstr(p, \"\\nTo: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 5;\n> -\t\t\t\tcount++;\n> -\t\t\t} else {\n> -\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 10;\n> -\t\t\t\tcount++;\n> -\t\t\t}\n> -\t\t}\n> +\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n> +\t\t\tcount++;\n>  \t\tp = strstr(p+5, \"\\nFrom \");\n>  \t\tif (!p)\n>  \t\t\tbreak;\n> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\n> index d1393ef197..1f77c14315 100755\n> --- a/t/t9001-send-email.sh\n> +++ b/t/t9001-send-email.sh\n> @@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n>  \ttest_cmp expect msgtxt2.to\n>  '\n>  \n> +test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n> +\twrite_script fake-imap-server <<-\\EOF &&\n> +\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n> +\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n> +\tcat >/dev/null\n> +\tEOF\n> +\ttest_config imap.tunnel ./fake-imap-server &&\n> +\tclean_fake_sendmail &&\n> +\tgit send-email \\\n> +\t\t--from=\"Example <from@example.com>\" \\\n> +\t\t--to=nobody@example.com \\\n> +\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n> +\t\tHEAD^ &&\n> +\t{\n> +\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n> +\t\tcat msgtxt1\n> +\t} >archive.mbox &&\n> +\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n> +\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n> +\ttest_grep \"(1/1) done\" err\n> +'\n> +\n> +test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n> +\ttest_config imap.tunnel ./fake-imap-server &&\n> +\tclean_fake_sendmail &&\n> +\tgit send-email \\\n> +\t\t--from=\"Example <from@example.com>\" \\\n> +\t\t--to=nobody@example.com \\\n> +\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n> +\t\t--imap-sent-folder=Sent \\\n> +\t\tHEAD^ 2>err &&\n> +\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n> +\ttest_grep \"(1/1) done\" err\n> +'\n> +\n>  test_done\n\n"},{"id":"555503","messageId":"CAHwyqnXfP=RrDj_5H=sR59TGD3dFGRk4SXDU6d08QZkOh_GL7A@mail.gmail.com","threadId":"66592","inReplyTo":"xmqqse211hck.fsf@gitster.g","subject":"Re: [PATCH 2/2] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-20T07:28:46Z","receivedAt":"2026-10-20T07:28:46Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> RFC 822/2822/5322 specifies these headers as case insensitive, so\n> you'd want to use istarts_with() instead, though.  I can see that\n> the original did not get it right, but we are making the wrongs in\n> the original right, so ...\n\nThanks!\n\nWould you like to see a test for case insensitive? Personally, I would\nskip it since I trust 'istarts_with'.\n\n\nHarald\n\n"},{"id":"555679","messageId":"pull.2456.v2.git.git.1792566469.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] Fix imap-send with reordered message headers","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-21T07:07:47Z","receivedAt":"2026-10-21T07:07:47Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"git imap-send rejected patches sent with git send-email, such as ones\nfetched from lore with b4, because it expected Date before Subject.\n\nChanges in v2:\n\n * Case insensitive header match according to RFC 822/2822/5322.\n\nHarald Nordgren (2):\n  imap-send: avoid leaking the tunnel command\n  imap-send: accept messages with Subject before Date\n\n imap-send.c           | 38 ++++++++++++++++++--------------------\n t/t9001-send-email.sh | 35 +++++++++++++++++++++++++++++++++++\n 2 files changed, 53 insertions(+), 20 deletions(-)\n\n\nbase-commit: 47fdf2208c5b94c8cc01c106b8f7c85efb91c926\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2456%2FHaraldNordgren%2Ffix%2Fimap-send-header-order-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2456/HaraldNordgren/fix/imap-send-header-order-v2\nPull-Request: https://github.com/git/git/pull/2456\n\nRange-diff vs v1:\n\n 1:  0784c16134 = 1:  b170b1f6d3 imap-send: avoid leaking the tunnel command\n 2:  94129fb718 ! 2:  c42827a122 imap-send: accept messages with Subject before Date\n     @@ Commit message\n          and git send-email writes Subject before Date.\n      \n          Accept the three headers in any order within the header block of each\n     -    message. The copy that git send-email --imap-sent-folder pipes to\n     +    message, and match their names case insensitively as RFC 5322\n     +    specifies. The copy that git send-email --imap-sent-folder pipes to\n          imap-send then passes the same check, so the special case added for it\n          in 04133f5bc4 (send-email: add ability to send a copy of sent emails to\n          an IMAP folder, 2025-08-12) is no longer needed.\n     @@ imap-send.c: static void wrap_in_html(struct strbuf *msg)\n      +\tbool from = false, date = false, subject = false;\n      +\n      +\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n     -+\t\tif (starts_with(line, \"From: \"))\n     ++\t\tif (istarts_with(line, \"From: \"))\n      +\t\t\tfrom = true;\n     -+\t\telse if (starts_with(line, \"Date: \"))\n     ++\t\telse if (istarts_with(line, \"Date: \"))\n      +\t\t\tdate = true;\n     -+\t\telse if (starts_with(line, \"Subject: \"))\n     ++\t\telse if (istarts_with(line, \"Subject: \"))\n      +\t\t\tsubject = true;\n      +\t}\n      +\treturn from && date && subject;\n\n-- \ngitgitgadget\n\n"},{"id":"555680","messageId":"b170b1f6d33d42b9441585fa8877a9e24d8d37a9.1792566469.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v2.git.git.1792566469.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] imap-send: avoid leaking the tunnel command","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-21T07:07:48Z","receivedAt":"2026-10-21T07:07:48Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen imap.tunnel is set, imap_open_store() starts the tunnel but never\nreleases the child_process used for it, which leaks its argument list.\nNo test sets imap.tunnel, so the leak checker has not caught this.\n\nOnly the pipes to the tunnel are needed once it is running, so release\nthe rest right after starting it.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bf1d2cf74d..6f76bade35 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1176,6 +1176,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n+\t\tchild_process_clear(&tunnel);\n \n \t\timap_info(\"OK\\n\");\n \t} else {\n-- \ngitgitgadget\n\n\n"},{"id":"555681","messageId":"c42827a1225e49db2e0855638e4e76a06f7d9eba.1792566469.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v2.git.git.1792566469.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-21T07:07:49Z","receivedAt":"2026-10-21T07:07:49Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nPatches sent with git send-email cannot be fed back into git imap-send\nafter fetching them from the mailing list archive, for example with\n\n    b4 mbox --single-message -o - <url> | git imap-send\n\nwhich fails with \"no messages found to send\". imap-send only counts a\nmessage when its From, Date and Subject headers come in that order,\nand git send-email writes Subject before Date.\n\nAccept the three headers in any order within the header block of each\nmessage, and match their names case insensitively as RFC 5322\nspecifies. The copy that git send-email --imap-sent-folder pipes to\nimap-send then passes the same check, so the special case added for it\nin 04133f5bc4 (send-email: add ability to send a copy of sent emails to\nan IMAP folder, 2025-08-12) is no longer needed.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 37 +++++++++++++++++--------------------\n t/t9001-send-email.sh | 35 +++++++++++++++++++++++++++++++++++\n 2 files changed, 52 insertions(+), 20 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 6f76bade35..7ae2eeddc9 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1453,32 +1453,29 @@ static void wrap_in_html(struct strbuf *msg)\n \t*msg = buf;\n }\n \n+static bool has_required_headers(const char *line)\n+{\n+\tbool from = false, date = false, subject = false;\n+\n+\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n+\t\tif (istarts_with(line, \"From: \"))\n+\t\t\tfrom = true;\n+\t\telse if (istarts_with(line, \"Date: \"))\n+\t\t\tdate = true;\n+\t\telse if (istarts_with(line, \"Subject: \"))\n+\t\t\tsubject = true;\n+\t}\n+\treturn from && date && subject;\n+}\n+\n static int count_messages(struct strbuf *all_msgs)\n {\n \tint count = 0;\n \tchar *p = all_msgs->buf;\n \n \twhile (1) {\n-\t\tif (starts_with(p, \"From \")) {\n-\t\t\tif (starts_with(p, \"From git-send-email\")) {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 7;\n-\t\t\t\tp = strstr(p, \"\\nTo: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 5;\n-\t\t\t\tcount++;\n-\t\t\t} else {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 10;\n-\t\t\t\tcount++;\n-\t\t\t}\n-\t\t}\n+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n+\t\t\tcount++;\n \t\tp = strstr(p+5, \"\\nFrom \");\n \t\tif (!p)\n \t\t\tbreak;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d1393ef197..1f77c14315 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n \ttest_cmp expect msgtxt2.to\n '\n \n+test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n+\twrite_script fake-imap-server <<-\\EOF &&\n+\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n+\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n+\tcat >/dev/null\n+\tEOF\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\tHEAD^ &&\n+\t{\n+\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n+\t\tcat msgtxt1\n+\t} >archive.mbox &&\n+\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\t--imap-sent-folder=Sent \\\n+\t\tHEAD^ 2>err &&\n+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"555695","messageId":"ath21xI1yLScKpuh@pks.im","threadId":"66592","inReplyTo":"b170b1f6d33d42b9441585fa8877a9e24d8d37a9.1792566469.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] imap-send: avoid leaking the tunnel command","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-21T08:24:55Z","receivedAt":"2026-10-21T08:24:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 21, 2026 at 07:07:48AM +0000, Harald Nordgren via GitGitGadget wrote:\n> diff --git a/imap-send.c b/imap-send.c\n> index bf1d2cf74d..6f76bade35 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1176,6 +1176,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n>  \n>  \t\timap->buf.sock.fd[0] = tunnel.out;\n>  \t\timap->buf.sock.fd[1] = tunnel.in;\n> +\t\tchild_process_clear(&tunnel);\n\nThis change plugs the memory leak, but the bigger question to me is why\nwe never call `finish_command()` on the child process at all. Is there a\nreason for that?\n\nPatrick\n\n"},{"id":"555696","messageId":"ath23vYy0n8UFLUB@pks.im","threadId":"66592","inReplyTo":"c42827a1225e49db2e0855638e4e76a06f7d9eba.1792566469.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] imap-send: accept messages with Subject before Date","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-21T08:25:02Z","receivedAt":"2026-10-21T08:25:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 21, 2026 at 07:07:49AM +0000, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Patches sent with git send-email cannot be fed back into git imap-send\n> after fetching them from the mailing list archive, for example with\n> \n>     b4 mbox --single-message -o - <url> | git imap-send\n> \n> which fails with \"no messages found to send\". imap-send only counts a\n> message when its From, Date and Subject headers come in that order,\n> and git send-email writes Subject before Date.\n> \n> Accept the three headers in any order within the header block of each\n> message, and match their names case insensitively as RFC 5322\n> specifies. The copy that git send-email --imap-sent-folder pipes to\n> imap-send then passes the same check, so the special case added for it\n> in 04133f5bc4 (send-email: add ability to send a copy of sent emails to\n> an IMAP folder, 2025-08-12) is no longer needed.\n\nAren't you fixing two different issues at once? I'd argue that this\nshould be done in separate commits.\n\n> diff --git a/imap-send.c b/imap-send.c\n> index 6f76bade35..7ae2eeddc9 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1453,32 +1453,29 @@ static void wrap_in_html(struct strbuf *msg)\n>  \t*msg = buf;\n>  }\n>  \n> +static bool has_required_headers(const char *line)\n> +{\n> +\tbool from = false, date = false, subject = false;\n> +\n> +\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n> +\t\tif (istarts_with(line, \"From: \"))\n> +\t\t\tfrom = true;\n> +\t\telse if (istarts_with(line, \"Date: \"))\n> +\t\t\tdate = true;\n> +\t\telse if (istarts_with(line, \"Subject: \"))\n> +\t\t\tsubject = true;\n> +\t}\n> +\n> +\treturn from && date && subject;\n> +}\n> +\n>  static int count_messages(struct strbuf *all_msgs)\n>  {\n>  \tint count = 0;\n>  \tchar *p = all_msgs->buf;\n>  \n>  \twhile (1) {\n> -\t\tif (starts_with(p, \"From \")) {\n> -\t\t\tif (starts_with(p, \"From git-send-email\")) {\n> -\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 7;\n> -\t\t\t\tp = strstr(p, \"\\nTo: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 5;\n> -\t\t\t\tcount++;\n> -\t\t\t} else {\n> -\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n> -\t\t\t\tif (!p) break;\n> -\t\t\t\tp += 10;\n> -\t\t\t\tcount++;\n> -\t\t\t}\n> -\t\t}\n> +\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n> +\t\t\tcount++;\n\nIn the preimage we special-cased when the line starts with \"From\ngit-send-email\", and in that case we didn't recognize \"Subject:\" lines.\nSo you're not only changing that we now accept the headers in any\narbitrary order, but you also make us accept \"Subject:\" lines when we\nsaw \"From git-send-email\". You don't really explain why that change is\nokay to do though.\n\n> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\n> index d1393ef197..1f77c14315 100755\n> --- a/t/t9001-send-email.sh\n> +++ b/t/t9001-send-email.sh\n> @@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n>  \ttest_cmp expect msgtxt2.to\n>  '\n>  \n> +test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n\nQuite weird that every single test carries this prerequisite. Makes me\nwonder why we don't just skip all tests at once.\n\nPatrick\n\n"},{"id":"555698","messageId":"CAHwyqnUwbq3XkLajsurk=X_=s3a8Z85Ai2raVmpsxK8HavfwZw@mail.gmail.com","threadId":"66592","inReplyTo":"ath23vYy0n8UFLUB@pks.im","subject":"Re: [PATCH v2 2/2] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-21T08:45:42Z","receivedAt":"2026-10-21T08:45:42Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> > Patches sent with git send-email cannot be fed back into git imap-send\n> > after fetching them from the mailing list archive, for example with\n> >\n> >     b4 mbox --single-message -o - <url> | git imap-send\n> >\n> > which fails with \"no messages found to send\". imap-send only counts a\n> > message when its From, Date and Subject headers come in that order,\n> > and git send-email writes Subject before Date.\n> >\n> > Accept the three headers in any order within the header block of each\n> > message, and match their names case insensitively as RFC 5322\n> > specifies. The copy that git send-email --imap-sent-folder pipes to\n> > imap-send then passes the same check, so the special case added for it\n> > in 04133f5bc4 (send-email: add ability to send a copy of sent emails to\n> > an IMAP folder, 2025-08-12) is no longer needed.\n>\n> Aren't you fixing two different issues at once? I'd argue that this\n> should be done in separate commits.\n\nGood point.\n\n> > @@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n> >       test_cmp expect msgtxt2.to\n> >  '\n> >\n> > +test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n>\n> Quite weird that every single test carries this prerequisite. Makes me\n> wonder why we don't just skip all tests at once.\n\nWhat do you mean here, do you want to remove the tests?\n\n\n\nHarald\n\n"},{"id":"555701","messageId":"CAHwyqnW9yRPabz2VCN27RoEw_kCmvGuKQwd-btuMK3vHJVnc8A@mail.gmail.com","threadId":"66592","inReplyTo":"ath21xI1yLScKpuh@pks.im","subject":"Re: [PATCH v2 1/2] imap-send: avoid leaking the tunnel command","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-21T08:53:43Z","receivedAt":"2026-10-21T08:53:43Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> >               imap->buf.sock.fd[0] = tunnel.out;\n> >               imap->buf.sock.fd[1] = tunnel.in;\n> > +             child_process_clear(&tunnel);\n>\n> This change plugs the memory leak, but the bigger question to me is why\n> we never call `finish_command()` on the child process at all. Is there a\n> reason for that?\n\nYeah, that's curious. I'll take a look at handling this better.\n\n\nHarald\n\n"},{"id":"555703","messageId":"atiHnUoDf-_h50V2@pks.im","threadId":"66592","inReplyTo":"CAHwyqnUwbq3XkLajsurk=X_=s3a8Z85Ai2raVmpsxK8HavfwZw@mail.gmail.com","subject":"Re: [PATCH v2 2/2] imap-send: accept messages with Subject before Date","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-21T09:36:29Z","receivedAt":"2026-10-21T09:36:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 21, 2026 at 10:45:42AM +0200, Harald Nordgren wrote:\n> > > @@ -2863,4 +2863,39 @@ test_expect_success $PREREQ '--compose handles to headers' '\n> > >       test_cmp expect msgtxt2.to\n> > >  '\n> > >\n> > > +test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n> >\n> > Quite weird that every single test carries this prerequisite. Makes me\n> > wonder why we don't just skip all tests at once.\n> \n> What do you mean here, do you want to remove the tests?\n\nNo, I rather think that we should have this at the beginning of the test\nfile:\n\n        if ! test_have_prereq PERL\n        then\n        \tskip_all='skipping send-email tests; Perl not available'\n        \ttest_done\n        fi\n\nThen we can drop the $PREREQ from all tests. But I just spotted that we\ndo some more gymanstics with another prereq that we optionally add\nlater, so this doesn't quite work as-is.\n\nOverall though this is fine, we don't have to fix it as part of this\npatch series.\n\nPatrick\n\n"},{"id":"555704","messageId":"CAHwyqnV3bse7pNiesYz_Tx2onT-SqVt+zdUoJ35LmafhQzDZ+w@mail.gmail.com","threadId":"66592","inReplyTo":"atiHnUoDf-_h50V2@pks.im","subject":"Re: [PATCH v2 2/2] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-21T09:59:35Z","receivedAt":"2026-10-21T09:59:35Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> > What do you mean here, do you want to remove the tests?\n>\n> No, I rather think that we should have this at the beginning of the test\n> file:\n>\n>         if ! test_have_prereq PERL\n>         then\n>                 skip_all='skipping send-email tests; Perl not available'\n>                 test_done\n>         fi\n>\n> Then we can drop the $PREREQ from all tests. But I just spotted that we\n> do some more gymanstics with another prereq that we optionally add\n> later, so this doesn't quite work as-is.\n>\n> Overall though this is fine, we don't have to fix it as part of this\n> patch series.\n\nAlright, maybe better to have this as #leftoverbits ?\n\n\nHarald\n\n"},{"id":"555824","messageId":"pull.2456.v3.git.git.1792652185.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH v3 0/4] Fix imap-send with reordered message headers","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-22T06:56:21Z","receivedAt":"2026-10-22T06:56:21Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"git imap-send rejected patches sent with git send-email, such as ones\nfetched from lore with b4, because it expected Date before Subject.\n\nChanges in v3:\n\n * Split concerns into different commits, commit message explains what they\n   do.\n * Handle tunnel leak in the proper place.\n\nChanges in v2:\n\n * Case insensitive header match according to RFC 822/2822/5322.\n\nHarald Nordgren (4):\n  imap-send: wait for the tunnel command to finish\n  imap-send: accept messages with Subject before Date\n  imap-send: check git send-email copies like other messages\n  imap-send: match header names case insensitively\n\n imap-send.c           | 57 ++++++++++++++++++++++---------------------\n t/t9001-send-email.sh | 35 ++++++++++++++++++++++++++\n 2 files changed, 64 insertions(+), 28 deletions(-)\n\n\nbase-commit: 47fdf2208c5b94c8cc01c106b8f7c85efb91c926\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2456%2FHaraldNordgren%2Ffix%2Fimap-send-header-order-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2456/HaraldNordgren/fix/imap-send-header-order-v3\nPull-Request: https://github.com/git/git/pull/2456\n\nRange-diff vs v2:\n\n 1:  b170b1f6d3 ! 1:  0f446c3c12 imap-send: avoid leaking the tunnel command\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    imap-send: avoid leaking the tunnel command\n     +    imap-send: wait for the tunnel command to finish\n      \n     -    When imap.tunnel is set, imap_open_store() starts the tunnel but never\n     -    releases the child_process used for it, which leaks its argument list.\n     -    No test sets imap.tunnel, so the leak checker has not caught this.\n     +    When imap.tunnel is set, git imap-send starts the tunnel command but\n     +    never waits for it to exit, and leaks the argument list it was started\n     +    with. The isync code that imap-send was derived from forked the tunnel\n     +    without ever reaping it, and c94d2dd080 (imap-send: use run-command\n     +    API for tunneling, 2009-10-19) carried that over.\n      \n     -    Only the pipes to the tunnel are needed once it is running, so release\n     -    the rest right after starting it.\n     +    Wait for the tunnel when closing the connection. By then imap-send has\n     +    closed its end of the pipes, so the tunnel sees the end of its input\n     +    and exits. No test sets imap.tunnel, so the leak checker has not\n     +    caught this.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## imap-send.c ##\n     +@@ imap-send.c: struct imap {\n     + \t/* command queue */\n     + \tint nexttag, num_in_progress, literal_pending;\n     + \tstruct imap_cmd *in_progress, **in_progress_append;\n     ++\tstruct child_process tunnel;\n     + \tstruct imap_buffer buf; /* this is BIG, so put it last */\n     + };\n     + \n     +@@ imap-send.c: static void imap_close_server(struct imap_store *ictx)\n     + \t\timap_exec(ictx, NULL, \"LOGOUT\");\n     + \t\tsocket_shutdown(&imap->buf.sock);\n     + \t}\n     ++\tif (ictx->cfg->tunnel)\n     ++\t\tfinish_command(&imap->tunnel);\n     + \tfree(imap);\n     + }\n     + \n      @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n     + \t/* open connection to IMAP server */\n     + \n     + \tif (srvc->tunnel) {\n     +-\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n     ++\t\tstruct child_process *tunnel = &imap->tunnel;\n     + \n     + \t\timap_info(\"Starting tunnel '%s'... \", srvc->tunnel);\n     + \n     +-\t\tstrvec_push(&tunnel.args, srvc->tunnel);\n     +-\t\ttunnel.use_shell = 1;\n     +-\t\ttunnel.in = -1;\n     +-\t\ttunnel.out = -1;\n     +-\t\tif (start_command(&tunnel))\n     ++\t\tchild_process_init(tunnel);\n     ++\t\tstrvec_push(&tunnel->args, srvc->tunnel);\n     ++\t\ttunnel->use_shell = 1;\n     ++\t\ttunnel->in = -1;\n     ++\t\ttunnel->out = -1;\n     ++\t\tif (start_command(tunnel))\n     + \t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n       \n     - \t\timap->buf.sock.fd[0] = tunnel.out;\n     - \t\timap->buf.sock.fd[1] = tunnel.in;\n     -+\t\tchild_process_clear(&tunnel);\n     +-\t\timap->buf.sock.fd[0] = tunnel.out;\n     +-\t\timap->buf.sock.fd[1] = tunnel.in;\n     ++\t\timap->buf.sock.fd[0] = tunnel->out;\n     ++\t\timap->buf.sock.fd[1] = tunnel->in;\n       \n       \t\timap_info(\"OK\\n\");\n       \t} else {\n 2:  c42827a122 ! 2:  38214d9abf imap-send: accept messages with Subject before Date\n     @@ Commit message\n          and git send-email writes Subject before Date.\n      \n          Accept the three headers in any order within the header block of each\n     -    message, and match their names case insensitively as RFC 5322\n     -    specifies. The copy that git send-email --imap-sent-folder pipes to\n     -    imap-send then passes the same check, so the special case added for it\n     -    in 04133f5bc4 (send-email: add ability to send a copy of sent emails to\n     -    an IMAP folder, 2025-08-12) is no longer needed.\n     +    message.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ imap-send.c: static void wrap_in_html(struct strbuf *msg)\n      +\tbool from = false, date = false, subject = false;\n      +\n      +\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n     -+\t\tif (istarts_with(line, \"From: \"))\n     ++\t\tif (starts_with(line, \"From: \"))\n      +\t\t\tfrom = true;\n     -+\t\telse if (istarts_with(line, \"Date: \"))\n     ++\t\telse if (starts_with(line, \"Date: \"))\n      +\t\t\tdate = true;\n     -+\t\telse if (istarts_with(line, \"Subject: \"))\n     ++\t\telse if (starts_with(line, \"Subject: \"))\n      +\t\t\tsubject = true;\n      +\t}\n      +\treturn from && date && subject;\n     @@ imap-send.c: static void wrap_in_html(struct strbuf *msg)\n       static int count_messages(struct strbuf *all_msgs)\n       {\n       \tint count = 0;\n     - \tchar *p = all_msgs->buf;\n     - \n     - \twhile (1) {\n     --\t\tif (starts_with(p, \"From \")) {\n     --\t\t\tif (starts_with(p, \"From git-send-email\")) {\n     --\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n     --\t\t\t\tif (!p) break;\n     --\t\t\t\tp += 7;\n     --\t\t\t\tp = strstr(p, \"\\nTo: \");\n     --\t\t\t\tif (!p) break;\n     --\t\t\t\tp += 5;\n     --\t\t\t\tcount++;\n     +@@ imap-send.c: static int count_messages(struct strbuf *all_msgs)\n     + \t\t\t\tif (!p) break;\n     + \t\t\t\tp += 5;\n     + \t\t\t\tcount++;\n      -\t\t\t} else {\n      -\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n      -\t\t\t\tif (!p) break;\n     @@ imap-send.c: static void wrap_in_html(struct strbuf *msg)\n      -\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n      -\t\t\t\tif (!p) break;\n      -\t\t\t\tp += 10;\n     --\t\t\t\tcount++;\n     --\t\t\t}\n     --\t\t}\n     -+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n     -+\t\t\tcount++;\n     - \t\tp = strstr(p+5, \"\\nFrom \");\n     - \t\tif (!p)\n     - \t\t\tbreak;\n     ++\t\t\t} else if (has_required_headers(p)) {\n     + \t\t\t\tcount++;\n     + \t\t\t}\n     + \t\t}\n      \n       ## t/t9001-send-email.sh ##\n      @@ t/t9001-send-email.sh: test_expect_success $PREREQ '--compose handles to headers' '\n     @@ t/t9001-send-email.sh: test_expect_success $PREREQ '--compose handles to headers\n      +\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n      +\ttest_grep \"(1/1) done\" err\n      +'\n     -+\n     -+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n     -+\ttest_config imap.tunnel ./fake-imap-server &&\n     -+\tclean_fake_sendmail &&\n     -+\tgit send-email \\\n     -+\t\t--from=\"Example <from@example.com>\" \\\n     -+\t\t--to=nobody@example.com \\\n     -+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n     -+\t\t--imap-sent-folder=Sent \\\n     -+\t\tHEAD^ 2>err &&\n     -+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n     -+\ttest_grep \"(1/1) done\" err\n     -+'\n      +\n       test_done\n -:  ---------- > 3:  213c4950e0 imap-send: check git send-email copies like other messages\n -:  ---------- > 4:  01add80af2 imap-send: match header names case insensitively\n\n-- \ngitgitgadget\n\n"},{"id":"555825","messageId":"0f446c3c12aa5963259fed2617da51c31b992321.1792652185.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v3.git.git.1792652185.gitgitgadget@gmail.com","subject":"[PATCH v3 1/4] imap-send: wait for the tunnel command to finish","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-22T06:56:22Z","receivedAt":"2026-10-22T06:56:22Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen imap.tunnel is set, git imap-send starts the tunnel command but\nnever waits for it to exit, and leaks the argument list it was started\nwith. The isync code that imap-send was derived from forked the tunnel\nwithout ever reaping it, and c94d2dd080 (imap-send: use run-command\nAPI for tunneling, 2009-10-19) carried that over.\n\nWait for the tunnel when closing the connection. By then imap-send has\nclosed its end of the pipes, so the tunnel sees the end of its input\nand exits. No test sets imap.tunnel, so the leak checker has not\ncaught this.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 20 ++++++++++++--------\n 1 file changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bf1d2cf74d..fa326c4ae2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -118,6 +118,7 @@ struct imap {\n \t/* command queue */\n \tint nexttag, num_in_progress, literal_pending;\n \tstruct imap_cmd *in_progress, **in_progress_append;\n+\tstruct child_process tunnel;\n \tstruct imap_buffer buf; /* this is BIG, so put it last */\n };\n \n@@ -858,6 +859,8 @@ static void imap_close_server(struct imap_store *ictx)\n \t\timap_exec(ictx, NULL, \"LOGOUT\");\n \t\tsocket_shutdown(&imap->buf.sock);\n \t}\n+\tif (ictx->cfg->tunnel)\n+\t\tfinish_command(&imap->tunnel);\n \tfree(imap);\n }\n \n@@ -1163,19 +1166,20 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t/* open connection to IMAP server */\n \n \tif (srvc->tunnel) {\n-\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n+\t\tstruct child_process *tunnel = &imap->tunnel;\n \n \t\timap_info(\"Starting tunnel '%s'... \", srvc->tunnel);\n \n-\t\tstrvec_push(&tunnel.args, srvc->tunnel);\n-\t\ttunnel.use_shell = 1;\n-\t\ttunnel.in = -1;\n-\t\ttunnel.out = -1;\n-\t\tif (start_command(&tunnel))\n+\t\tchild_process_init(tunnel);\n+\t\tstrvec_push(&tunnel->args, srvc->tunnel);\n+\t\ttunnel->use_shell = 1;\n+\t\ttunnel->in = -1;\n+\t\ttunnel->out = -1;\n+\t\tif (start_command(tunnel))\n \t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n \n-\t\timap->buf.sock.fd[0] = tunnel.out;\n-\t\timap->buf.sock.fd[1] = tunnel.in;\n+\t\timap->buf.sock.fd[0] = tunnel->out;\n+\t\timap->buf.sock.fd[1] = tunnel->in;\n \n \t\timap_info(\"OK\\n\");\n \t} else {\n-- \ngitgitgadget\n\n\n"},{"id":"555826","messageId":"38214d9abf62fa4f8d179ce15e633aa1157ae3e3.1792652185.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v3.git.git.1792652185.gitgitgadget@gmail.com","subject":"[PATCH v3 2/4] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-22T06:56:23Z","receivedAt":"2026-10-22T06:56:23Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nPatches sent with git send-email cannot be fed back into git imap-send\nafter fetching them from the mailing list archive, for example with\n\n    b4 mbox --single-message -o - <url> | git imap-send\n\nwhich fails with \"no messages found to send\". imap-send only counts a\nmessage when its From, Date and Subject headers come in that order,\nand git send-email writes Subject before Date.\n\nAccept the three headers in any order within the header block of each\nmessage.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 24 ++++++++++++++++--------\n t/t9001-send-email.sh | 22 ++++++++++++++++++++++\n 2 files changed, 38 insertions(+), 8 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex fa326c4ae2..ce2321d42e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1456,6 +1456,21 @@ static void wrap_in_html(struct strbuf *msg)\n \t*msg = buf;\n }\n \n+static bool has_required_headers(const char *line)\n+{\n+\tbool from = false, date = false, subject = false;\n+\n+\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n+\t\tif (starts_with(line, \"From: \"))\n+\t\t\tfrom = true;\n+\t\telse if (starts_with(line, \"Date: \"))\n+\t\t\tdate = true;\n+\t\telse if (starts_with(line, \"Subject: \"))\n+\t\t\tsubject = true;\n+\t}\n+\treturn from && date && subject;\n+}\n+\n static int count_messages(struct strbuf *all_msgs)\n {\n \tint count = 0;\n@@ -1471,14 +1486,7 @@ static int count_messages(struct strbuf *all_msgs)\n \t\t\t\tif (!p) break;\n \t\t\t\tp += 5;\n \t\t\t\tcount++;\n-\t\t\t} else {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 10;\n+\t\t\t} else if (has_required_headers(p)) {\n \t\t\t\tcount++;\n \t\t\t}\n \t\t}\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d1393ef197..d105fcb41c 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2863,4 +2863,26 @@ test_expect_success $PREREQ '--compose handles to headers' '\n \ttest_cmp expect msgtxt2.to\n '\n \n+test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n+\twrite_script fake-imap-server <<-\\EOF &&\n+\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n+\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n+\tcat >/dev/null\n+\tEOF\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\tHEAD^ &&\n+\t{\n+\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n+\t\tcat msgtxt1\n+\t} >archive.mbox &&\n+\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"555827","messageId":"213c4950e053441ad81a0d5f6569e7a2ea63e25c.1792652185.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v3.git.git.1792652185.gitgitgadget@gmail.com","subject":"[PATCH v3 3/4] imap-send: check git send-email copies like other messages","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-22T06:56:24Z","receivedAt":"2026-10-22T06:56:24Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nThe copy that git send-email --imap-sent-folder pipes to imap-send\nstarts with a \"From git-send-email\" line. 04133f5bc4 (send-email: add\nability to send a copy of sent emails to an IMAP folder, 2025-08-12)\nmade imap-send count such a message when it has From and To headers,\nwithout looking for Date and Subject. Without that special case the\ncopy would not be counted at all, because git send-email writes\nSubject before Date.\n\nNow that the headers are accepted in any order, the special case is no\nlonger needed. The copy carries the same headers git send-email sends\nover SMTP, which always include From, Subject and Date, so it passes\nthe check for other messages. A To header is no longer required, the\nsame as for any other message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 15 ++-------------\n t/t9001-send-email.sh | 13 +++++++++++++\n 2 files changed, 15 insertions(+), 13 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex ce2321d42e..1091825dfb 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1477,19 +1477,8 @@ static int count_messages(struct strbuf *all_msgs)\n \tchar *p = all_msgs->buf;\n \n \twhile (1) {\n-\t\tif (starts_with(p, \"From \")) {\n-\t\t\tif (starts_with(p, \"From git-send-email\")) {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 7;\n-\t\t\t\tp = strstr(p, \"\\nTo: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 5;\n-\t\t\t\tcount++;\n-\t\t\t} else if (has_required_headers(p)) {\n-\t\t\t\tcount++;\n-\t\t\t}\n-\t\t}\n+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n+\t\t\tcount++;\n \t\tp = strstr(p+5, \"\\nFrom \");\n \t\tif (!p)\n \t\t\tbreak;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d105fcb41c..1f77c14315 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2885,4 +2885,17 @@ test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archiv\n \ttest_grep \"(1/1) done\" err\n '\n \n+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\t--imap-sent-folder=Sent \\\n+\t\tHEAD^ 2>err &&\n+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"555828","messageId":"01add80af28d6a23e44a1d3149e2c2a875c00c84.1792652185.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v3.git.git.1792652185.gitgitgadget@gmail.com","subject":"[PATCH v3 4/4] imap-send: match header names case insensitively","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-22T06:56:25Z","receivedAt":"2026-10-22T06:56:25Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nimap-send says \"no messages found to send\" for a message whose headers\nare spelled \"from:\", \"date:\" or \"subject:\", although RFC 5322 treats\nheader field names as case insensitive. Match the three names in any\ncase.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 1091825dfb..21e20d7720 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1461,11 +1461,11 @@ static bool has_required_headers(const char *line)\n \tbool from = false, date = false, subject = false;\n \n \twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n-\t\tif (starts_with(line, \"From: \"))\n+\t\tif (istarts_with(line, \"From: \"))\n \t\t\tfrom = true;\n-\t\telse if (starts_with(line, \"Date: \"))\n+\t\telse if (istarts_with(line, \"Date: \"))\n \t\t\tdate = true;\n-\t\telse if (starts_with(line, \"Subject: \"))\n+\t\telse if (istarts_with(line, \"Subject: \"))\n \t\t\tsubject = true;\n \t}\n \treturn from && date && subject;\n-- \ngitgitgadget\n\n"},{"id":"556291","messageId":"at8gbEmbW8b5cDjo@pks.im","threadId":"66592","inReplyTo":"0f446c3c12aa5963259fed2617da51c31b992321.1792652185.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/4] imap-send: wait for the tunnel command to finish","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-26T09:42:13Z","receivedAt":"2026-10-26T09:42:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 06:56:22AM +0000, Harald Nordgren via GitGitGadget wrote:\n> diff --git a/imap-send.c b/imap-send.c\n> index bf1d2cf74d..fa326c4ae2 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -118,6 +118,7 @@ struct imap {\n>  \t/* command queue */\n>  \tint nexttag, num_in_progress, literal_pending;\n>  \tstruct imap_cmd *in_progress, **in_progress_append;\n> +\tstruct child_process tunnel;\n>  \tstruct imap_buffer buf; /* this is BIG, so put it last */\n>  };\n>  \n> @@ -858,6 +859,8 @@ static void imap_close_server(struct imap_store *ictx)\n>  \t\timap_exec(ictx, NULL, \"LOGOUT\");\n>  \t\tsocket_shutdown(&imap->buf.sock);\n>  \t}\n> +\tif (ictx->cfg->tunnel)\n> +\t\tfinish_command(&imap->tunnel);\n\nHmm, okay. Do we _know_ that `ictx->cfg->tunnel` is always properly\ninitialized in case we call `imap_close_server()`? Or are there error\npaths where it might not be initialized yet? If the latter I think we\nmight see weird behaviour here as the function does not seem to be safe\nwhen called on an uninitialized command.\n\nMaybe we should make `imap->tunnel` an allocated pointer instead so that\nwe know to only finish it when allocated and initialized?\n\n> @@ -1163,19 +1166,20 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n>  \t/* open connection to IMAP server */\n>  \n>  \tif (srvc->tunnel) {\n> -\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n> +\t\tstruct child_process *tunnel = &imap->tunnel;\n\nNit: I would have avoided this local variable, it doesn't help us anyway\nto shrink the diff.\n\nPatrick\n\n"},{"id":"556292","messageId":"at8gelQLS3vuidF4@pks.im","threadId":"66592","inReplyTo":"213c4950e053441ad81a0d5f6569e7a2ea63e25c.1792652185.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/4] imap-send: check git send-email copies like other messages","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-26T09:42:18Z","receivedAt":"2026-10-26T09:42:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 06:56:24AM +0000, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> The copy that git send-email --imap-sent-folder pipes to imap-send\n> starts with a \"From git-send-email\" line. 04133f5bc4 (send-email: add\n> ability to send a copy of sent emails to an IMAP folder, 2025-08-12)\n> made imap-send count such a message when it has From and To headers,\n> without looking for Date and Subject. Without that special case the\n> copy would not be counted at all, because git send-email writes\n> Subject before Date.\n> \n> Now that the headers are accepted in any order, the special case is no\n> longer needed. The copy carries the same headers git send-email sends\n> over SMTP, which always include From, Subject and Date, so it passes\n> the check for other messages. A To header is no longer required, the\n> same as for any other message.\n\nYou argue from the perspective of git-send-email(1) generating the mbox.\nBut what if somebody writes an mbox via a different script that doesn't\nwrite a Date or Subject line? Based on RFC 5322, the Date header is\nmandatory, so I think it's safe to assume it always exists. But the\nSubject header is only specified as \"SHOULD exist\".\n\nNow we probably don't care about such edge cases, so tightening the\nrules is likely going to be fine. But I don't think that's sufficiently\nexplained.\n\nPatrick\n\n"},{"id":"556293","messageId":"at8ghTkzSQE7KWVg@pks.im","threadId":"66592","inReplyTo":"01add80af28d6a23e44a1d3149e2c2a875c00c84.1792652185.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 4/4] imap-send: match header names case insensitively","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-26T09:42:29Z","receivedAt":"2026-10-26T09:42:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 06:56:25AM +0000, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> imap-send says \"no messages found to send\" for a message whose headers\n> are spelled \"from:\", \"date:\" or \"subject:\", although RFC 5322 treats\n> header field names as case insensitive. Match the three names in any\n> case.\n\nShould we have a test for this, as well?\n\nPatrick\n\n"},{"id":"556299","messageId":"CAHwyqnXAncLAM1q8m_OZmv=c_gajrgR4mK4gCWJ_zEGHcj+N3A@mail.gmail.com","threadId":"66592","inReplyTo":"at8gbEmbW8b5cDjo@pks.im","subject":"Re: [PATCH v3 1/4] imap-send: wait for the tunnel command to finish","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-26T09:59:58Z","receivedAt":"2026-10-26T09:59:58Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> > +     if (ictx->cfg->tunnel)\n> > +             finish_command(&imap->tunnel);\n>\n> Hmm, okay. Do we _know_ that `ictx->cfg->tunnel` is always properly\n> initialized in case we call `imap_close_server()`? Or are there error\n> paths where it might not be initialized yet? If the latter I think we\n> might see weird behaviour here as the function does not seem to be safe\n> when called on an uninitialized command.\n>\n> Maybe we should make `imap->tunnel` an allocated pointer instead so that\n> we know to only finish it when allocated and initialized?\n\nGood idea.\n\n> > @@ -1163,19 +1166,20 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n> >       /* open connection to IMAP server */\n> >\n> >       if (srvc->tunnel) {\n> > -             struct child_process tunnel = CHILD_PROCESS_INIT;\n> > +             struct child_process *tunnel = &imap->tunnel;\n>\n> Nit: I would have avoided this local variable, it doesn't help us anyway\n> to shrink the diff.\n\nOk.\n\n\nHarald\n\n"},{"id":"556300","messageId":"CAHwyqnX=pnrhATbZ7UmkYHabFZ0oKyK45Mfp+Zh+2U=gj2Ei9A@mail.gmail.com","threadId":"66592","inReplyTo":"at8gelQLS3vuidF4@pks.im","subject":"Re: [PATCH v3 3/4] imap-send: check git send-email copies like other messages","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-26T10:02:49Z","receivedAt":"2026-10-26T10:02:49Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> > The copy that git send-email --imap-sent-folder pipes to imap-send\n> > starts with a \"From git-send-email\" line. 04133f5bc4 (send-email: add\n> > ability to send a copy of sent emails to an IMAP folder, 2025-08-12)\n> > made imap-send count such a message when it has From and To headers,\n> > without looking for Date and Subject. Without that special case the\n> > copy would not be counted at all, because git send-email writes\n> > Subject before Date.\n> >\n> > Now that the headers are accepted in any order, the special case is no\n> > longer needed. The copy carries the same headers git send-email sends\n> > over SMTP, which always include From, Subject and Date, so it passes\n> > the check for other messages. A To header is no longer required, the\n> > same as for any other message.\n>\n> You argue from the perspective of git-send-email(1) generating the mbox.\n> But what if somebody writes an mbox via a different script that doesn't\n> write a Date or Subject line? Based on RFC 5322, the Date header is\n> mandatory, so I think it's safe to assume it always exists. But the\n> Subject header is only specified as \"SHOULD exist\".\n>\n> Now we probably don't care about such edge cases, so tightening the\n> rules is likely going to be fine. But I don't think that's sufficiently\n> explained.\n\nThanks, very valuable to have another set of eyes here!\n\n\nHarald\n\n"},{"id":"556447","messageId":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH v4 0/4] Fix imap-send with reordered message headers","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-27T08:31:15Z","receivedAt":"2026-10-27T08:31:15Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"git imap-send rejected patches sent with git send-email, such as ones\nfetched from lore with b4, because it expected Date before Subject.\n\nChanges in v4:\n\n * Store the tunnel as an allocated pointer, so it is only finished when it\n   was started, and drop the local tunnel variable\n * Add a test for lowercase from:, date: and subject: headers\n * Explain in the commit message why dropping the From git-send-email\n   special case is fine for mboxes written by other scripts\n\nChanges in v3:\n\n * Split concerns into different commits, commit message explains what they\n   do.\n * Handle tunnel leak in the proper place.\n\nChanges in v2:\n\n * Case insensitive header match according to RFC 822/2822/5322.\n\nHarald Nordgren (4):\n  imap-send: wait for the tunnel command to finish\n  imap-send: accept messages with Subject before Date\n  imap-send: check git send-email copies like other messages\n  imap-send: match header names case insensitively\n\n imap-send.c           | 60 ++++++++++++++++++++++---------------------\n t/t9001-send-email.sh | 50 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 81 insertions(+), 29 deletions(-)\n\n\nbase-commit: 7e34d8174dcda3435d1404c9a30bef46afe6c13e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2456%2FHaraldNordgren%2Ffix%2Fimap-send-header-order-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2456/HaraldNordgren/fix/imap-send-header-order-v4\nPull-Request: https://github.com/git/git/pull/2456\n\nRange-diff vs v3:\n\n 1:  0f446c3c12 ! 1:  8941ae12bb imap-send: wait for the tunnel command to finish\n     @@ imap-send.c: struct imap {\n       \t/* command queue */\n       \tint nexttag, num_in_progress, literal_pending;\n       \tstruct imap_cmd *in_progress, **in_progress_append;\n     -+\tstruct child_process tunnel;\n     ++\tstruct child_process *tunnel;\n       \tstruct imap_buffer buf; /* this is BIG, so put it last */\n       };\n       \n     @@ imap-send.c: static void imap_close_server(struct imap_store *ictx)\n       \t\timap_exec(ictx, NULL, \"LOGOUT\");\n       \t\tsocket_shutdown(&imap->buf.sock);\n       \t}\n     -+\tif (ictx->cfg->tunnel)\n     -+\t\tfinish_command(&imap->tunnel);\n     ++\tif (imap->tunnel) {\n     ++\t\tfinish_command(imap->tunnel);\n     ++\t\tfree(imap->tunnel);\n     ++\t}\n       \tfree(imap);\n       }\n       \n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n       \n       \tif (srvc->tunnel) {\n      -\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n     -+\t\tstruct child_process *tunnel = &imap->tunnel;\n     - \n     +-\n       \t\timap_info(\"Starting tunnel '%s'... \", srvc->tunnel);\n       \n      -\t\tstrvec_push(&tunnel.args, srvc->tunnel);\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n      -\t\ttunnel.in = -1;\n      -\t\ttunnel.out = -1;\n      -\t\tif (start_command(&tunnel))\n     -+\t\tchild_process_init(tunnel);\n     -+\t\tstrvec_push(&tunnel->args, srvc->tunnel);\n     -+\t\ttunnel->use_shell = 1;\n     -+\t\ttunnel->in = -1;\n     -+\t\ttunnel->out = -1;\n     -+\t\tif (start_command(tunnel))\n     ++\t\timap->tunnel = xmalloc(sizeof(*imap->tunnel));\n     ++\t\tchild_process_init(imap->tunnel);\n     ++\t\tstrvec_push(&imap->tunnel->args, srvc->tunnel);\n     ++\t\timap->tunnel->use_shell = 1;\n     ++\t\timap->tunnel->in = -1;\n     ++\t\timap->tunnel->out = -1;\n     ++\t\tif (start_command(imap->tunnel))\n       \t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n       \n      -\t\timap->buf.sock.fd[0] = tunnel.out;\n      -\t\timap->buf.sock.fd[1] = tunnel.in;\n     -+\t\timap->buf.sock.fd[0] = tunnel->out;\n     -+\t\timap->buf.sock.fd[1] = tunnel->in;\n     ++\t\timap->buf.sock.fd[0] = imap->tunnel->out;\n     ++\t\timap->buf.sock.fd[1] = imap->tunnel->in;\n       \n       \t\timap_info(\"OK\\n\");\n       \t} else {\n 2:  38214d9abf = 2:  ad3158b336 imap-send: accept messages with Subject before Date\n 3:  213c4950e0 ! 3:  919e38c31d imap-send: check git send-email copies like other messages\n     @@ Commit message\n          the check for other messages. A To header is no longer required, the\n          same as for any other message.\n      \n     +    This makes the check stricter when some other script writes an mbox\n     +    with a \"From git-send-email\" line. RFC 5322 requires a Date header but\n     +    only recommends Subject, so a valid message from such a script may\n     +    lack a Subject and would no longer be counted. imap-send has always\n     +    required a Subject under any other \"From \" line, though, and only\n     +    refuses an mbox when none of its messages are counted, so treating\n     +    these copies like the rest should not affect anyone in practice.\n     +\n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## imap-send.c ##\n 4:  01add80af2 ! 4:  5b4a08bbda imap-send: match header names case insensitively\n     @@ imap-send.c: static bool has_required_headers(const char *line)\n       \t\t\tsubject = true;\n       \t}\n       \treturn from && date && subject;\n     +\n     + ## t/t9001-send-email.sh ##\n     +@@ t/t9001-send-email.sh: test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch'\n     + \ttest_grep \"(1/1) done\" err\n     + '\n     + \n     ++test_expect_success $PREREQ 'imap-send matches header names in any case' '\n     ++\ttest_config imap.tunnel ./fake-imap-server &&\n     ++\tcat >lowercase.mbox <<-\\EOF &&\n     ++\tFrom mboxrd@z Thu Jan  1 00:00:00 1970\n     ++\tfrom: Example <from@example.com>\n     ++\tdate: Thu, 1 Jan 1970 00:00:00 +0000\n     ++\tsubject: [PATCH] lowercase headers\n     ++\n     ++\tbody\n     ++\tEOF\n     ++\tgit imap-send --folder=INBOX <lowercase.mbox 2>err &&\n     ++\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n     ++\ttest_grep \"(1/1) done\" err\n     ++'\n     ++\n     + test_done\n\n-- \ngitgitgadget\n\n"},{"id":"556448","messageId":"8941ae12bb9acf8f3b780314dd5d4d740c1ace19.1793089879.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","subject":"[PATCH v4 1/4] imap-send: wait for the tunnel command to finish","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-27T08:31:16Z","receivedAt":"2026-10-27T08:31:16Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen imap.tunnel is set, git imap-send starts the tunnel command but\nnever waits for it to exit, and leaks the argument list it was started\nwith. The isync code that imap-send was derived from forked the tunnel\nwithout ever reaping it, and c94d2dd080 (imap-send: use run-command\nAPI for tunneling, 2009-10-19) carried that over.\n\nWait for the tunnel when closing the connection. By then imap-send has\nclosed its end of the pipes, so the tunnel sees the end of its input\nand exits. No test sets imap.tunnel, so the leak checker has not\ncaught this.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bf1d2cf74d..7e74c37a72 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -118,6 +118,7 @@ struct imap {\n \t/* command queue */\n \tint nexttag, num_in_progress, literal_pending;\n \tstruct imap_cmd *in_progress, **in_progress_append;\n+\tstruct child_process *tunnel;\n \tstruct imap_buffer buf; /* this is BIG, so put it last */\n };\n \n@@ -858,6 +859,10 @@ static void imap_close_server(struct imap_store *ictx)\n \t\timap_exec(ictx, NULL, \"LOGOUT\");\n \t\tsocket_shutdown(&imap->buf.sock);\n \t}\n+\tif (imap->tunnel) {\n+\t\tfinish_command(imap->tunnel);\n+\t\tfree(imap->tunnel);\n+\t}\n \tfree(imap);\n }\n \n@@ -1163,19 +1168,19 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t/* open connection to IMAP server */\n \n \tif (srvc->tunnel) {\n-\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n-\n \t\timap_info(\"Starting tunnel '%s'... \", srvc->tunnel);\n \n-\t\tstrvec_push(&tunnel.args, srvc->tunnel);\n-\t\ttunnel.use_shell = 1;\n-\t\ttunnel.in = -1;\n-\t\ttunnel.out = -1;\n-\t\tif (start_command(&tunnel))\n+\t\timap->tunnel = xmalloc(sizeof(*imap->tunnel));\n+\t\tchild_process_init(imap->tunnel);\n+\t\tstrvec_push(&imap->tunnel->args, srvc->tunnel);\n+\t\timap->tunnel->use_shell = 1;\n+\t\timap->tunnel->in = -1;\n+\t\timap->tunnel->out = -1;\n+\t\tif (start_command(imap->tunnel))\n \t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n \n-\t\timap->buf.sock.fd[0] = tunnel.out;\n-\t\timap->buf.sock.fd[1] = tunnel.in;\n+\t\timap->buf.sock.fd[0] = imap->tunnel->out;\n+\t\timap->buf.sock.fd[1] = imap->tunnel->in;\n \n \t\timap_info(\"OK\\n\");\n \t} else {\n-- \ngitgitgadget\n\n\n"},{"id":"556449","messageId":"ad3158b3367d00fb78d2fa5a55b82d5f634c3d61.1793089879.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","subject":"[PATCH v4 2/4] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-27T08:31:17Z","receivedAt":"2026-10-27T08:31:17Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nPatches sent with git send-email cannot be fed back into git imap-send\nafter fetching them from the mailing list archive, for example with\n\n    b4 mbox --single-message -o - <url> | git imap-send\n\nwhich fails with \"no messages found to send\". imap-send only counts a\nmessage when its From, Date and Subject headers come in that order,\nand git send-email writes Subject before Date.\n\nAccept the three headers in any order within the header block of each\nmessage.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 24 ++++++++++++++++--------\n t/t9001-send-email.sh | 22 ++++++++++++++++++++++\n 2 files changed, 38 insertions(+), 8 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 7e74c37a72..8977a934f8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1457,6 +1457,21 @@ static void wrap_in_html(struct strbuf *msg)\n \t*msg = buf;\n }\n \n+static bool has_required_headers(const char *line)\n+{\n+\tbool from = false, date = false, subject = false;\n+\n+\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n+\t\tif (starts_with(line, \"From: \"))\n+\t\t\tfrom = true;\n+\t\telse if (starts_with(line, \"Date: \"))\n+\t\t\tdate = true;\n+\t\telse if (starts_with(line, \"Subject: \"))\n+\t\t\tsubject = true;\n+\t}\n+\treturn from && date && subject;\n+}\n+\n static int count_messages(struct strbuf *all_msgs)\n {\n \tint count = 0;\n@@ -1472,14 +1487,7 @@ static int count_messages(struct strbuf *all_msgs)\n \t\t\t\tif (!p) break;\n \t\t\t\tp += 5;\n \t\t\t\tcount++;\n-\t\t\t} else {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 10;\n+\t\t\t} else if (has_required_headers(p)) {\n \t\t\t\tcount++;\n \t\t\t}\n \t\t}\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d1393ef197..d105fcb41c 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2863,4 +2863,26 @@ test_expect_success $PREREQ '--compose handles to headers' '\n \ttest_cmp expect msgtxt2.to\n '\n \n+test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n+\twrite_script fake-imap-server <<-\\EOF &&\n+\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n+\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n+\tcat >/dev/null\n+\tEOF\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\tHEAD^ &&\n+\t{\n+\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n+\t\tcat msgtxt1\n+\t} >archive.mbox &&\n+\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"556450","messageId":"919e38c31d83d3cb01aae49e59112868ea9a1fed.1793089879.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","subject":"[PATCH v4 3/4] imap-send: check git send-email copies like other messages","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-27T08:31:18Z","receivedAt":"2026-10-27T08:31:18Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nThe copy that git send-email --imap-sent-folder pipes to imap-send\nstarts with a \"From git-send-email\" line. 04133f5bc4 (send-email: add\nability to send a copy of sent emails to an IMAP folder, 2025-08-12)\nmade imap-send count such a message when it has From and To headers,\nwithout looking for Date and Subject. Without that special case the\ncopy would not be counted at all, because git send-email writes\nSubject before Date.\n\nNow that the headers are accepted in any order, the special case is no\nlonger needed. The copy carries the same headers git send-email sends\nover SMTP, which always include From, Subject and Date, so it passes\nthe check for other messages. A To header is no longer required, the\nsame as for any other message.\n\nThis makes the check stricter when some other script writes an mbox\nwith a \"From git-send-email\" line. RFC 5322 requires a Date header but\nonly recommends Subject, so a valid message from such a script may\nlack a Subject and would no longer be counted. imap-send has always\nrequired a Subject under any other \"From \" line, though, and only\nrefuses an mbox when none of its messages are counted, so treating\nthese copies like the rest should not affect anyone in practice.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 15 ++-------------\n t/t9001-send-email.sh | 13 +++++++++++++\n 2 files changed, 15 insertions(+), 13 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 8977a934f8..706c517e51 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1478,19 +1478,8 @@ static int count_messages(struct strbuf *all_msgs)\n \tchar *p = all_msgs->buf;\n \n \twhile (1) {\n-\t\tif (starts_with(p, \"From \")) {\n-\t\t\tif (starts_with(p, \"From git-send-email\")) {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 7;\n-\t\t\t\tp = strstr(p, \"\\nTo: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 5;\n-\t\t\t\tcount++;\n-\t\t\t} else if (has_required_headers(p)) {\n-\t\t\t\tcount++;\n-\t\t\t}\n-\t\t}\n+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n+\t\t\tcount++;\n \t\tp = strstr(p+5, \"\\nFrom \");\n \t\tif (!p)\n \t\t\tbreak;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d105fcb41c..1f77c14315 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2885,4 +2885,17 @@ test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archiv\n \ttest_grep \"(1/1) done\" err\n '\n \n+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\t--imap-sent-folder=Sent \\\n+\t\tHEAD^ 2>err &&\n+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"556451","messageId":"5b4a08bbdadac95a84f9c865e697584f45bd67af.1793089879.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","subject":"[PATCH v4 4/4] imap-send: match header names case insensitively","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-27T08:31:19Z","receivedAt":"2026-10-27T08:31:19Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nimap-send says \"no messages found to send\" for a message whose headers\nare spelled \"from:\", \"date:\" or \"subject:\", although RFC 5322 treats\nheader field names as case insensitive. Match the three names in any\ncase.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           |  6 +++---\n t/t9001-send-email.sh | 15 +++++++++++++++\n 2 files changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 706c517e51..b8dac92c70 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1462,11 +1462,11 @@ static bool has_required_headers(const char *line)\n \tbool from = false, date = false, subject = false;\n \n \twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n-\t\tif (starts_with(line, \"From: \"))\n+\t\tif (istarts_with(line, \"From: \"))\n \t\t\tfrom = true;\n-\t\telse if (starts_with(line, \"Date: \"))\n+\t\telse if (istarts_with(line, \"Date: \"))\n \t\t\tdate = true;\n-\t\telse if (starts_with(line, \"Subject: \"))\n+\t\telse if (istarts_with(line, \"Subject: \"))\n \t\t\tsubject = true;\n \t}\n \treturn from && date && subject;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 1f77c14315..897473e786 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2898,4 +2898,19 @@ test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch'\n \ttest_grep \"(1/1) done\" err\n '\n \n+test_expect_success $PREREQ 'imap-send matches header names in any case' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tcat >lowercase.mbox <<-\\EOF &&\n+\tFrom mboxrd@z Thu Jan  1 00:00:00 1970\n+\tfrom: Example <from@example.com>\n+\tdate: Thu, 1 Jan 1970 00:00:00 +0000\n+\tsubject: [PATCH] lowercase headers\n+\n+\tbody\n+\tEOF\n+\tgit imap-send --folder=INBOX <lowercase.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"556480","messageId":"auCEGdYgWQy5R8Px@pks.im","threadId":"66592","inReplyTo":"919e38c31d83d3cb01aae49e59112868ea9a1fed.1793089879.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/4] imap-send: check git send-email copies like other messages","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-27T10:59:47Z","receivedAt":"2026-10-27T10:59:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 27, 2026 at 08:31:18AM +0000, Harald Nordgren via GitGitGadget wrote:\n> This makes the check stricter when some other script writes an mbox\n> with a \"From git-send-email\" line. RFC 5322 requires a Date header but\n> only recommends Subject, so a valid message from such a script may\n> lack a Subject and would no longer be counted. imap-send has always\n> required a Subject under any other \"From \" line, though, and only\n> refuses an mbox when none of its messages are counted, so treating\n> these copies like the rest should not affect anyone in practice.\n\nMakes sense. I think it's highly unlikely that anyone ever formats patch\nseries without a Subject and then expects everything to \"just work\". So\neven though we are tightening checks, I don't see that this will have\nany negative consequences.\n\nPatrick\n\n"},{"id":"556481","messageId":"auCEKSc60ZnLKjBE@pks.im","threadId":"66592","inReplyTo":"pull.2456.v4.git.git.1793089879.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/4] Fix imap-send with reordered message headers","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-27T10:59:53Z","receivedAt":"2026-10-27T10:59:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 27, 2026 at 08:31:15AM +0000, Harald Nordgren via GitGitGadget wrote:\n> git imap-send rejected patches sent with git send-email, such as ones\n> fetched from lore with b4, because it expected Date before Subject.\n> \n> Changes in v4:\n> \n>  * Store the tunnel as an allocated pointer, so it is only finished when it\n>    was started, and drop the local tunnel variable\n>  * Add a test for lowercase from:, date: and subject: headers\n>  * Explain in the commit message why dropping the From git-send-email\n>    special case is fine for mboxes written by other scripts\n\nThanks, I'm happy with this version.\n\nPatrick\n\n"},{"id":"556792","messageId":"xmqqfqxo60m1.fsf@gitster.g","threadId":"66592","inReplyTo":"auCEKSc60ZnLKjBE@pks.im","subject":"Re: [PATCH v4 0/4] Fix imap-send with reordered message headers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-29T14:29:58Z","receivedAt":"2026-10-29T14:29:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Tue, Oct 27, 2026 at 08:31:15AM +0000, Harald Nordgren via GitGitGadget wrote:\n>> git imap-send rejected patches sent with git send-email, such as ones\n>> fetched from lore with b4, because it expected Date before Subject.\n>> \n>> Changes in v4:\n>> \n>>  * Store the tunnel as an allocated pointer, so it is only finished when it\n>>    was started, and drop the local tunnel variable\n>>  * Add a test for lowercase from:, date: and subject: headers\n>>  * Explain in the commit message why dropping the From git-send-email\n>>    special case is fine for mboxes written by other scripts\n>\n> Thanks, I'm happy with this version.\n>\n> Patrick\n\nThanks.\n\n"},{"id":"556980","messageId":"xmqqqzh6u7i3.fsf@gitster.g","threadId":"66592","inReplyTo":"ad3158b3367d00fb78d2fa5a55b82d5f634c3d61.1793089879.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/4] imap-send: accept messages with Subject before Date","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-30T22:51:48Z","receivedAt":"2026-10-30T22:51:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +static bool has_required_headers(const char *line)\n> +{\n> +\tbool from = false, date = false, subject = false;\n> +\n> +\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n> +\t\tif (starts_with(line, \"From: \"))\n> +\t\t\tfrom = true;\n> +\t\telse if (starts_with(line, \"Date: \"))\n> +\t\t\tdate = true;\n> +\t\telse if (starts_with(line, \"Subject: \"))\n> +\t\t\tsubject = true;\n> +\t}\n> +\treturn from && date && subject;\n> +}\n\nThe above assumes that an empty line that denotes the end of the\nheader can be detected by looking for '\\n\\n' in the line[].  So this\nmay be scanning thru to the end of the message including the body,\nbecause each and every line ends with CRLF and there is no two\nconsecutive LF appearing in the line.\n\nI admit that it was eons ago I read RFCs on IMAP (like RFC 2060),\nbut I recall that its definition explicitly called for CRLF line\nendings, and existing code in imap-send.c before these patches seems\nto expect that it is reading CRLF delimited lines.\n\nI didn't look at other parts of the series, but this one stood out.\n\n\n"},{"id":"556993","messageId":"CAHwyqnWM_GDhN_OSCrrwsuxuqwnc+mxi17H6Gut4mA=hAPPNFA@mail.gmail.com","threadId":"66592","inReplyTo":"xmqqqzh6u7i3.fsf@gitster.g","subject":"Re: [PATCH v4 2/4] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-10-31T07:58:20Z","receivedAt":"2026-10-31T07:58:20Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> > +static bool has_required_headers(const char *line)\n> > +{\n> > +     bool from = false, date = false, subject = false;\n> > +\n> > +     while ((line = strchr(line, '\\n')) && *++line != '\\n') {\n> > +             if (starts_with(line, \"From: \"))\n> > +                     from = true;\n> > +             else if (starts_with(line, \"Date: \"))\n> > +                     date = true;\n> > +             else if (starts_with(line, \"Subject: \"))\n> > +                     subject = true;\n> > +     }\n> > +     return from && date && subject;\n> > +}\n>\n> The above assumes that an empty line that denotes the end of the\n> header can be detected by looking for '\\n\\n' in the line[].  So this\n> may be scanning thru to the end of the message including the body,\n> because each and every line ends with CRLF and there is no two\n> consecutive LF appearing in the line.\n>\n> I admit that it was eons ago I read RFCs on IMAP (like RFC 2060),\n> but I recall that its definition explicitly called for CRLF line\n> endings, and existing code in imap-send.c before these patches seems\n> to expect that it is reading CRLF delimited lines.\n\nThanks for pointing this out, I'll take a look.\n\n\nHarald\n\n"},{"id":"557057","messageId":"pull.2456.v5.git.git.1793521297.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.git.git.1792437558.gitgitgadget@gmail.com","subject":"[PATCH v5 0/4] imap-send: fix with reordered message headers","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-11-01T08:21:33Z","receivedAt":"2026-11-01T08:21:33Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"git imap-send rejected patches sent with git send-email, such as ones\nfetched from lore with b4, because it expected Date before Subject.\n\nChanges in v5:\n\n * End the header block at an empty line in a CRLF mbox as well, so headers\n   in the body or in later messages no longer count. Add a test.\n\nChanges in v4:\n\n * Store the tunnel as an allocated pointer, so it is only finished when it\n   was started, and drop the local tunnel variable\n * Add a test for lowercase from:, date: and subject: headers\n * Explain in the commit message why dropping the From git-send-email\n   special case is fine for mboxes written by other scripts\n\nChanges in v3:\n\n * Split concerns into different commits, commit message explains what they\n   do.\n * Handle tunnel leak in the proper place.\n\nChanges in v2:\n\n * Case insensitive header match according to RFC 822/2822/5322.\n\nHarald Nordgren (4):\n  imap-send: wait for the tunnel command to finish\n  imap-send: accept messages with Subject before Date\n  imap-send: check git send-email copies like other messages\n  imap-send: match header names case insensitively\n\n imap-send.c           | 61 ++++++++++++++++++++-------------------\n t/t9001-send-email.sh | 66 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 98 insertions(+), 29 deletions(-)\n\n\nbase-commit: 50fe5cc63f9cd6e89b87a2c9ccf96a444f4cfdb7\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2456%2FHaraldNordgren%2Ffix%2Fimap-send-header-order-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2456/HaraldNordgren/fix/imap-send-header-order-v5\nPull-Request: https://github.com/git/git/pull/2456\n\nRange-diff vs v4:\n\n 1:  8941ae12bb = 1:  cb27ac3a27 imap-send: wait for the tunnel command to finish\n 2:  ad3158b336 ! 2:  fa90c64a5c imap-send: accept messages with Subject before Date\n     @@ Commit message\n          and git send-email writes Subject before Date.\n      \n          Accept the three headers in any order within the header block of each\n     -    message.\n     +    message, which ends at the first empty line whether the mbox uses LF or\n     +    CRLF line endings.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ imap-send.c: static void wrap_in_html(struct strbuf *msg)\n      +{\n      +\tbool from = false, date = false, subject = false;\n      +\n     -+\twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n     ++\twhile ((line = strchr(line, '\\n')) &&\n     ++\t       *++line != '\\n' && !starts_with(line, \"\\r\\n\")) {\n      +\t\tif (starts_with(line, \"From: \"))\n      +\t\t\tfrom = true;\n      +\t\telse if (starts_with(line, \"Date: \"))\n     @@ t/t9001-send-email.sh: test_expect_success $PREREQ '--compose handles to headers\n      +\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n      +\ttest_grep \"(1/1) done\" err\n      +'\n     ++\n     ++test_expect_success $PREREQ 'imap-send ignores headers in the body of a CRLF mbox' '\n     ++\ttest_config imap.tunnel ./fake-imap-server &&\n     ++\tq_to_cr >crlf.mbox <<-\\EOF &&\n     ++\tFrom mboxrd@z Thu Jan  1 00:00:00 1970Q\n     ++\tFrom: Example <from@example.com>Q\n     ++\tSubject: Fwd: originalQ\n     ++\tQ\n     ++\t---------- Forwarded message ---------Q\n     ++\tFrom: Someone <someone@example.com>Q\n     ++\tDate: Thu, 1 Jan 1970 00:00:00 +0000Q\n     ++\tSubject: originalQ\n     ++\tEOF\n     ++\ttest_must_fail git imap-send --folder=INBOX <crlf.mbox 2>err &&\n     ++\ttest_grep \"no messages found to send\" err\n     ++'\n      +\n       test_done\n 3:  919e38c31d ! 3:  8646185486 imap-send: check git send-email copies like other messages\n     @@ imap-send.c: static int count_messages(struct strbuf *all_msgs)\n       \t\t\tbreak;\n      \n       ## t/t9001-send-email.sh ##\n     -@@ t/t9001-send-email.sh: test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archiv\n     - \ttest_grep \"(1/1) done\" err\n     +@@ t/t9001-send-email.sh: test_expect_success $PREREQ 'imap-send ignores headers in the body of a CRLF mbo\n     + \ttest_grep \"no messages found to send\" err\n       '\n       \n      +test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n 4:  5b4a08bbda ! 4:  e7d1d7cd72 imap-send: match header names case insensitively\n     @@ Commit message\n      \n       ## imap-send.c ##\n      @@ imap-send.c: static bool has_required_headers(const char *line)\n     - \tbool from = false, date = false, subject = false;\n       \n     - \twhile ((line = strchr(line, '\\n')) && *++line != '\\n') {\n     + \twhile ((line = strchr(line, '\\n')) &&\n     + \t       *++line != '\\n' && !starts_with(line, \"\\r\\n\")) {\n      -\t\tif (starts_with(line, \"From: \"))\n      +\t\tif (istarts_with(line, \"From: \"))\n       \t\t\tfrom = true;\n\n-- \ngitgitgadget\n\n"},{"id":"557058","messageId":"cb27ac3a27eb9b6d3b951d34422b6bb6a43b8626.1793521297.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v5.git.git.1793521297.gitgitgadget@gmail.com","subject":"[PATCH v5 1/4] imap-send: wait for the tunnel command to finish","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-11-01T08:21:34Z","receivedAt":"2026-11-01T08:21:34Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen imap.tunnel is set, git imap-send starts the tunnel command but\nnever waits for it to exit, and leaks the argument list it was started\nwith. The isync code that imap-send was derived from forked the tunnel\nwithout ever reaping it, and c94d2dd080 (imap-send: use run-command\nAPI for tunneling, 2009-10-19) carried that over.\n\nWait for the tunnel when closing the connection. By then imap-send has\nclosed its end of the pipes, so the tunnel sees the end of its input\nand exits. No test sets imap.tunnel, so the leak checker has not\ncaught this.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bf1d2cf74d..7e74c37a72 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -118,6 +118,7 @@ struct imap {\n \t/* command queue */\n \tint nexttag, num_in_progress, literal_pending;\n \tstruct imap_cmd *in_progress, **in_progress_append;\n+\tstruct child_process *tunnel;\n \tstruct imap_buffer buf; /* this is BIG, so put it last */\n };\n \n@@ -858,6 +859,10 @@ static void imap_close_server(struct imap_store *ictx)\n \t\timap_exec(ictx, NULL, \"LOGOUT\");\n \t\tsocket_shutdown(&imap->buf.sock);\n \t}\n+\tif (imap->tunnel) {\n+\t\tfinish_command(imap->tunnel);\n+\t\tfree(imap->tunnel);\n+\t}\n \tfree(imap);\n }\n \n@@ -1163,19 +1168,19 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t/* open connection to IMAP server */\n \n \tif (srvc->tunnel) {\n-\t\tstruct child_process tunnel = CHILD_PROCESS_INIT;\n-\n \t\timap_info(\"Starting tunnel '%s'... \", srvc->tunnel);\n \n-\t\tstrvec_push(&tunnel.args, srvc->tunnel);\n-\t\ttunnel.use_shell = 1;\n-\t\ttunnel.in = -1;\n-\t\ttunnel.out = -1;\n-\t\tif (start_command(&tunnel))\n+\t\timap->tunnel = xmalloc(sizeof(*imap->tunnel));\n+\t\tchild_process_init(imap->tunnel);\n+\t\tstrvec_push(&imap->tunnel->args, srvc->tunnel);\n+\t\timap->tunnel->use_shell = 1;\n+\t\timap->tunnel->in = -1;\n+\t\timap->tunnel->out = -1;\n+\t\tif (start_command(imap->tunnel))\n \t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n \n-\t\timap->buf.sock.fd[0] = tunnel.out;\n-\t\timap->buf.sock.fd[1] = tunnel.in;\n+\t\timap->buf.sock.fd[0] = imap->tunnel->out;\n+\t\timap->buf.sock.fd[1] = imap->tunnel->in;\n \n \t\timap_info(\"OK\\n\");\n \t} else {\n-- \ngitgitgadget\n\n\n"},{"id":"557059","messageId":"fa90c64a5cab1964988a84b61f4b7af0d9e8b4ad.1793521297.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v5.git.git.1793521297.gitgitgadget@gmail.com","subject":"[PATCH v5 2/4] imap-send: accept messages with Subject before Date","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-11-01T08:21:35Z","receivedAt":"2026-11-01T08:21:35Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nPatches sent with git send-email cannot be fed back into git imap-send\nafter fetching them from the mailing list archive, for example with\n\n    b4 mbox --single-message -o - <url> | git imap-send\n\nwhich fails with \"no messages found to send\". imap-send only counts a\nmessage when its From, Date and Subject headers come in that order,\nand git send-email writes Subject before Date.\n\nAccept the three headers in any order within the header block of each\nmessage, which ends at the first empty line whether the mbox uses LF or\nCRLF line endings.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 25 +++++++++++++++++--------\n t/t9001-send-email.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+), 8 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 7e74c37a72..391bd8b96a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1457,6 +1457,22 @@ static void wrap_in_html(struct strbuf *msg)\n \t*msg = buf;\n }\n \n+static bool has_required_headers(const char *line)\n+{\n+\tbool from = false, date = false, subject = false;\n+\n+\twhile ((line = strchr(line, '\\n')) &&\n+\t       *++line != '\\n' && !starts_with(line, \"\\r\\n\")) {\n+\t\tif (starts_with(line, \"From: \"))\n+\t\t\tfrom = true;\n+\t\telse if (starts_with(line, \"Date: \"))\n+\t\t\tdate = true;\n+\t\telse if (starts_with(line, \"Subject: \"))\n+\t\t\tsubject = true;\n+\t}\n+\treturn from && date && subject;\n+}\n+\n static int count_messages(struct strbuf *all_msgs)\n {\n \tint count = 0;\n@@ -1472,14 +1488,7 @@ static int count_messages(struct strbuf *all_msgs)\n \t\t\t\tif (!p) break;\n \t\t\t\tp += 5;\n \t\t\t\tcount++;\n-\t\t\t} else {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nDate: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp = strstr(p+7, \"\\nSubject: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 10;\n+\t\t\t} else if (has_required_headers(p)) {\n \t\t\t\tcount++;\n \t\t\t}\n \t\t}\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex d1393ef197..4611e6d4a6 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2863,4 +2863,42 @@ test_expect_success $PREREQ '--compose handles to headers' '\n \ttest_cmp expect msgtxt2.to\n '\n \n+test_expect_success $PREREQ 'imap-send accepts a sent patch from the list archive' '\n+\twrite_script fake-imap-server <<-\\EOF &&\n+\tprintf \"* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+] ready\\r\\n\"\n+\tprintf \"1 OK\\r\\n2 OK\\r\\n3 OK\\r\\n\"\n+\tcat >/dev/null\n+\tEOF\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\tHEAD^ &&\n+\t{\n+\t\techo \"From mboxrd@z Thu Jan  1 00:00:00 1970\" &&\n+\t\tcat msgtxt1\n+\t} >archive.mbox &&\n+\tgit imap-send --folder=INBOX <archive.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n+test_expect_success $PREREQ 'imap-send ignores headers in the body of a CRLF mbox' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tq_to_cr >crlf.mbox <<-\\EOF &&\n+\tFrom mboxrd@z Thu Jan  1 00:00:00 1970Q\n+\tFrom: Example <from@example.com>Q\n+\tSubject: Fwd: originalQ\n+\tQ\n+\t---------- Forwarded message ---------Q\n+\tFrom: Someone <someone@example.com>Q\n+\tDate: Thu, 1 Jan 1970 00:00:00 +0000Q\n+\tSubject: originalQ\n+\tEOF\n+\ttest_must_fail git imap-send --folder=INBOX <crlf.mbox 2>err &&\n+\ttest_grep \"no messages found to send\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"557060","messageId":"86461854867919ba4acd684ce360040fe6c9ddeb.1793521297.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v5.git.git.1793521297.gitgitgadget@gmail.com","subject":"[PATCH v5 3/4] imap-send: check git send-email copies like other messages","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-11-01T08:21:36Z","receivedAt":"2026-11-01T08:21:36Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nThe copy that git send-email --imap-sent-folder pipes to imap-send\nstarts with a \"From git-send-email\" line. 04133f5bc4 (send-email: add\nability to send a copy of sent emails to an IMAP folder, 2025-08-12)\nmade imap-send count such a message when it has From and To headers,\nwithout looking for Date and Subject. Without that special case the\ncopy would not be counted at all, because git send-email writes\nSubject before Date.\n\nNow that the headers are accepted in any order, the special case is no\nlonger needed. The copy carries the same headers git send-email sends\nover SMTP, which always include From, Subject and Date, so it passes\nthe check for other messages. A To header is no longer required, the\nsame as for any other message.\n\nThis makes the check stricter when some other script writes an mbox\nwith a \"From git-send-email\" line. RFC 5322 requires a Date header but\nonly recommends Subject, so a valid message from such a script may\nlack a Subject and would no longer be counted. imap-send has always\nrequired a Subject under any other \"From \" line, though, and only\nrefuses an mbox when none of its messages are counted, so treating\nthese copies like the rest should not affect anyone in practice.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           | 15 ++-------------\n t/t9001-send-email.sh | 13 +++++++++++++\n 2 files changed, 15 insertions(+), 13 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 391bd8b96a..d7172078c3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1479,19 +1479,8 @@ static int count_messages(struct strbuf *all_msgs)\n \tchar *p = all_msgs->buf;\n \n \twhile (1) {\n-\t\tif (starts_with(p, \"From \")) {\n-\t\t\tif (starts_with(p, \"From git-send-email\")) {\n-\t\t\t\tp = strstr(p+5, \"\\nFrom: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 7;\n-\t\t\t\tp = strstr(p, \"\\nTo: \");\n-\t\t\t\tif (!p) break;\n-\t\t\t\tp += 5;\n-\t\t\t\tcount++;\n-\t\t\t} else if (has_required_headers(p)) {\n-\t\t\t\tcount++;\n-\t\t\t}\n-\t\t}\n+\t\tif (starts_with(p, \"From \") && has_required_headers(p))\n+\t\t\tcount++;\n \t\tp = strstr(p+5, \"\\nFrom \");\n \t\tif (!p)\n \t\t\tbreak;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 4611e6d4a6..5e03b350c1 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2901,4 +2901,17 @@ test_expect_success $PREREQ 'imap-send ignores headers in the body of a CRLF mbo\n \ttest_grep \"no messages found to send\" err\n '\n \n+test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tclean_fake_sendmail &&\n+\tgit send-email \\\n+\t\t--from=\"Example <from@example.com>\" \\\n+\t\t--to=nobody@example.com \\\n+\t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n+\t\t--imap-sent-folder=Sent \\\n+\t\tHEAD^ 2>err &&\n+\ttest_grep \"^Sending 1 message to Sent folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n\n"},{"id":"557061","messageId":"e7d1d7cd72563a8109f94137236cab3d0157d39f.1793521297.git.gitgitgadget@gmail.com","threadId":"66592","inReplyTo":"pull.2456.v5.git.git.1793521297.gitgitgadget@gmail.com","subject":"[PATCH v5 4/4] imap-send: match header names case insensitively","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-11-01T08:21:37Z","receivedAt":"2026-11-01T08:21:37Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nimap-send says \"no messages found to send\" for a message whose headers\nare spelled \"from:\", \"date:\" or \"subject:\", although RFC 5322 treats\nheader field names as case insensitive. Match the three names in any\ncase.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n imap-send.c           |  6 +++---\n t/t9001-send-email.sh | 15 +++++++++++++++\n 2 files changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex d7172078c3..1b5850a80e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1463,11 +1463,11 @@ static bool has_required_headers(const char *line)\n \n \twhile ((line = strchr(line, '\\n')) &&\n \t       *++line != '\\n' && !starts_with(line, \"\\r\\n\")) {\n-\t\tif (starts_with(line, \"From: \"))\n+\t\tif (istarts_with(line, \"From: \"))\n \t\t\tfrom = true;\n-\t\telse if (starts_with(line, \"Date: \"))\n+\t\telse if (istarts_with(line, \"Date: \"))\n \t\t\tdate = true;\n-\t\telse if (starts_with(line, \"Subject: \"))\n+\t\telse if (istarts_with(line, \"Subject: \"))\n \t\t\tsubject = true;\n \t}\n \treturn from && date && subject;\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 5e03b350c1..37d21424c9 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -2914,4 +2914,19 @@ test_expect_success $PREREQ '--imap-sent-folder stores a copy of the sent patch'\n \ttest_grep \"(1/1) done\" err\n '\n \n+test_expect_success $PREREQ 'imap-send matches header names in any case' '\n+\ttest_config imap.tunnel ./fake-imap-server &&\n+\tcat >lowercase.mbox <<-\\EOF &&\n+\tFrom mboxrd@z Thu Jan  1 00:00:00 1970\n+\tfrom: Example <from@example.com>\n+\tdate: Thu, 1 Jan 1970 00:00:00 +0000\n+\tsubject: [PATCH] lowercase headers\n+\n+\tbody\n+\tEOF\n+\tgit imap-send --folder=INBOX <lowercase.mbox 2>err &&\n+\ttest_grep \"^Sending 1 message to INBOX folder\" err &&\n+\ttest_grep \"(1/1) done\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"}]}