{"thread":{"id":"56877","subject":"[PATCH 1/2] doc: git-format-patch: specify the option --always","startedAt":"2021-11-12T05:00:46Z","lastAt":"2021-12-11T09:22:45Z","messageCount":129,"participants":["Aleen via GitGitGadget","René Scharfe","Junio C Hamano","Aleen","Aleen 徐沛文","Phillip Wood","Bagas Sanjaya","Ævar Arnfjörð Bjarmason","Eric Sunshine","Aleen 徐沛文 via GitGitGadget","Johannes Schindelin","徐沛文 (Aleen) via GitGitGadget","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"440957","messageId":"71e6989375c9b8dd00151481e0bb19c991e673f3.1636693095.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.git.1636693095.gitgitgadget@gmail.com","subject":"[PATCH 1/2] doc: git-format-patch: specify the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T04:58:14Z","receivedAt":"2021-11-12T05:00:46Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..a9f2bf94182 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -30,6 +30,7 @@ SYNOPSIS\n \t\t   [--range-diff=<previous> [--creation-factor=<percent>]]\n \t\t   [--filename-max-length=<n>]\n \t\t   [--progress]\n+\t\t   [--always]\n \t\t   [<common diff options>]\n \t\t   [ <since> | <revision range> ]\n \n@@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.\n --progress::\n \tShow progress reports on stderr as patches are generated.\n \n+--always::\n+\tPatch commits with detailed commit messages,\n+\teven if they emit no changes. (see linkgit:git-diff-tree[1])\n+\n CONFIGURATION\n -------------\n You can specify extra mail header lines to be added to each message,\n-- \ngitgitgadget\n\n"},{"id":"440958","messageId":"pull.1076.git.1636693095.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":null,"subject":"[PATCH 0/2] am: support --always option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T04:58:13Z","receivedAt":"2021-11-12T05:00:46Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with empty commit message, git-am should support\napplying and committing with empty patches.\n\nAleen (2):\n  doc: git-format-patch: specify the option --always\n  am: support --always option to am empty commits\n\n Documentation/git-am.txt           |  5 +++++\n Documentation/git-format-patch.txt |  5 +++++\n builtin/am.c                       | 18 ++++++++++++++++--\n t/t4150-am.sh                      | 25 +++++++++++++++++++++++++\n 4 files changed, 51 insertions(+), 2 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n-- \ngitgitgadget\n"},{"id":"440959","messageId":"59b1417da3754add11e72692ec11c09e486269e4.1636693095.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.git.1636693095.gitgitgadget@gmail.com","subject":"[PATCH 2/2] am: support --always option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T04:58:15Z","receivedAt":"2021-11-12T05:00:46Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  5 +++++\n builtin/am.c             | 18 ++++++++++++++++--\n t/t4150-am.sh            | 25 +++++++++++++++++++++++++\n 3 files changed, 46 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..de5d11e404c 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--always]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -159,6 +160,10 @@ default.   You can use `--no-utf8` to override this.\n \tcountermand both `commit.gpgSign` configuration variable, and\n \tearlier `--gpg-sign`.\n \n+--always::\n+\tApply patches of commits with detailed commit messages,\n+\teven if they emit no changes. (see linkgit:git-format-patch[1])\n+\n --continue::\n -r::\n --resolved::\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..d11efc16f92 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -124,6 +124,8 @@ struct am_state {\n \tint ignore_date;\n \tint allow_rerere_autoupdate;\n \tconst char *sign_commit;\n+\tint always;\n+\tint empty_commit;\n \tint rebasing;\n };\n \n@@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t}\n \n \tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n+\t\tif (state->always) {\n+\t\t\tstate->empty_commit = 1;\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tstrbuf_addstr(&msg, \"\\n\\n\");\n@@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (state->empty_commit)\n+\t\t\tgoto commit;\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1836,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2367,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_BOOL(0, \"always\", &state.always,\n+\t\t\tN_(\"always apply patch event if the patch is empty\")),\n+\t\tOPT_HIDDEN_BOOL(0, \"empty-commit\", &state.empty_commit,\n+\t\t\tN_(\"(internal use for skipping git-apply to empty commits)\")),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..5b3617857a8 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'am a real empty patch with the --always option' '\n+\trm -fr .git/rebase-apply &&\n+\tgit reset --hard &&\n+\ttest_must_fail git am --always empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'am a patch with empty commits' '\n+\tgrep \"empty commit\" empty-commit.patch &&\n+\trm -fr .git/rebase-apply &&\n+\tgit reset --hard &&\n+\tgit checkout empty-commit^ &&\n+\tgit am --always empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit cat-file commit HEAD >actual &&\n+\ttest_i18ngrep \"empty commit\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"440961","messageId":"6da5e984-1dcf-4c55-976c-49ffb8d128bf@web.de","threadId":"56877","inReplyTo":"pull.1076.git.1636693095.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] am: support --always option to am empty commits","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2021-11-12T06:17:47Z","receivedAt":"2021-11-12T06:17:56Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 12.11.21 um 05:58 schrieb Aleen via GitGitGadget:\n> Since that git has supported the --always option for the git-format-patch\n> command to create a patch with empty commit message, git-am should support\n> applying and committing with empty patches.\n\nThe symmetry is compelling, but \"always\" is quite generic.  I can see\ne.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n\ngit commit and cherry-pick have --allow-empty, which is (a bit) more\nspecific.  That seems to me a better option name to copy for a commit-\ncreating command like git am.\n\nRené\n"},{"id":"440962","messageId":"xmqqy25tj2fi.fsf@gitster.g","threadId":"56877","inReplyTo":"6da5e984-1dcf-4c55-976c-49ffb8d128bf@web.de","subject":"Re: [PATCH 0/2] am: support --always option to am empty commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-12T06:47:13Z","receivedAt":"2021-11-12T06:47:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> Am 12.11.21 um 05:58 schrieb Aleen via GitGitGadget:\n>> Since that git has supported the --always option for the git-format-patch\n>> command to create a patch with empty commit message, git-am should support\n>> applying and committing with empty patches.\n>\n> The symmetry is compelling, but \"always\" is quite generic.  I can see\n> e.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n\nWhat symmetry?\n\n> git commit and cherry-pick have --allow-empty, which is (a bit) more\n> specific.  That seems to me a better option name to copy for a commit-\n> creating command like git am.\n\nThat one I can believe, even though I do not necessarily think it is\na good idea to add such an option.\n"},{"id":"440963","messageId":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.git.1636693095.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] am: support --allow-empty option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T06:53:56Z","receivedAt":"2021-11-12T06:54:08Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with empty commit message, git-am should support\napplying and committing with empty patches.\n\nChanges since v1:\n\n * test: am: add the case when not passing the --always option\n * chore: am: rename the --always option to --allow-empty\n\nAleen (4):\n  doc: git-format-patch: specify the option --always\n  am: support --always option to am empty commits\n  test: am: add the case when not passing the --always option\n  chore: am: rename the --always option to --allow-empty\n\n Documentation/git-am.txt           |  5 +++++\n Documentation/git-format-patch.txt |  5 +++++\n builtin/am.c                       | 18 +++++++++++++--\n t/t4150-am.sh                      | 35 ++++++++++++++++++++++++++++++\n 4 files changed, 61 insertions(+), 2 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v1:\n\n 1:  71e6989375c = 1:  71e6989375c doc: git-format-patch: specify the option --always\n 2:  59b1417da37 = 2:  59b1417da37 am: support --always option to am empty commits\n -:  ----------- > 3:  da024ced668 test: am: add the case when not passing the --always option\n -:  ----------- > 4:  45e9720f40b chore: am: rename the --always option to --allow-empty\n\n-- \ngitgitgadget\n"},{"id":"440964","messageId":"71e6989375c9b8dd00151481e0bb19c991e673f3.1636700040.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","subject":"[PATCH v2 1/4] doc: git-format-patch: specify the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T06:53:57Z","receivedAt":"2021-11-12T06:54:10Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..a9f2bf94182 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -30,6 +30,7 @@ SYNOPSIS\n \t\t   [--range-diff=<previous> [--creation-factor=<percent>]]\n \t\t   [--filename-max-length=<n>]\n \t\t   [--progress]\n+\t\t   [--always]\n \t\t   [<common diff options>]\n \t\t   [ <since> | <revision range> ]\n \n@@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.\n --progress::\n \tShow progress reports on stderr as patches are generated.\n \n+--always::\n+\tPatch commits with detailed commit messages,\n+\teven if they emit no changes. (see linkgit:git-diff-tree[1])\n+\n CONFIGURATION\n -------------\n You can specify extra mail header lines to be added to each message,\n-- \ngitgitgadget\n\n"},{"id":"440965","messageId":"59b1417da3754add11e72692ec11c09e486269e4.1636700040.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","subject":"[PATCH v2 2/4] am: support --always option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T06:53:58Z","receivedAt":"2021-11-12T06:54:12Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  5 +++++\n builtin/am.c             | 18 ++++++++++++++++--\n t/t4150-am.sh            | 25 +++++++++++++++++++++++++\n 3 files changed, 46 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..de5d11e404c 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--always]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -159,6 +160,10 @@ default.   You can use `--no-utf8` to override this.\n \tcountermand both `commit.gpgSign` configuration variable, and\n \tearlier `--gpg-sign`.\n \n+--always::\n+\tApply patches of commits with detailed commit messages,\n+\teven if they emit no changes. (see linkgit:git-format-patch[1])\n+\n --continue::\n -r::\n --resolved::\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..d11efc16f92 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -124,6 +124,8 @@ struct am_state {\n \tint ignore_date;\n \tint allow_rerere_autoupdate;\n \tconst char *sign_commit;\n+\tint always;\n+\tint empty_commit;\n \tint rebasing;\n };\n \n@@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t}\n \n \tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n+\t\tif (state->always) {\n+\t\t\tstate->empty_commit = 1;\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tstrbuf_addstr(&msg, \"\\n\\n\");\n@@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (state->empty_commit)\n+\t\t\tgoto commit;\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1836,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2367,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_BOOL(0, \"always\", &state.always,\n+\t\t\tN_(\"always apply patch event if the patch is empty\")),\n+\t\tOPT_HIDDEN_BOOL(0, \"empty-commit\", &state.empty_commit,\n+\t\t\tN_(\"(internal use for skipping git-apply to empty commits)\")),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..5b3617857a8 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'am a real empty patch with the --always option' '\n+\trm -fr .git/rebase-apply &&\n+\tgit reset --hard &&\n+\ttest_must_fail git am --always empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'am a patch with empty commits' '\n+\tgrep \"empty commit\" empty-commit.patch &&\n+\trm -fr .git/rebase-apply &&\n+\tgit reset --hard &&\n+\tgit checkout empty-commit^ &&\n+\tgit am --always empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit cat-file commit HEAD >actual &&\n+\ttest_i18ngrep \"empty commit\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"440966","messageId":"da024ced6686bfcae3e824c14ba0a7550c8317ad.1636700040.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","subject":"[PATCH v2 3/4] test: am: add the case when not passing the --always option","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T06:53:59Z","receivedAt":"2021-11-12T06:54:13Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n t/t4150-am.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 5b3617857a8..364c61ba198 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1166,7 +1166,17 @@ test_expect_success 'am a real empty patch with the --always option' '\n \ttest_cmp expected actual\n '\n \n-test_expect_success 'am a patch with empty commits' '\n+test_expect_success 'am a patch of empty commits without the --always option' '\n+\tgrep \"empty commit\" empty-commit.patch &&\n+\trm -fr .git/rebase-apply &&\n+\tgit reset --hard &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'am a patch of empty commits with the --always option' '\n \tgrep \"empty commit\" empty-commit.patch &&\n \trm -fr .git/rebase-apply &&\n \tgit reset --hard &&\n-- \ngitgitgadget\n\n"},{"id":"440967","messageId":"45e9720f40bbf71ceea72ac464e3343f9b47a04f.1636700040.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","subject":"[PATCH v2 4/4] chore: am: rename the --always option to --allow-empty","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-12T06:54:00Z","receivedAt":"2021-11-12T06:54:15Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  4 ++--\n builtin/am.c             |  8 ++++----\n t/t4150-am.sh            | 10 +++++-----\n 3 files changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex de5d11e404c..6ed844af9c6 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n-\t [--always]\n+\t [--allow-empty]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -160,7 +160,7 @@ default.   You can use `--no-utf8` to override this.\n \tcountermand both `commit.gpgSign` configuration variable, and\n \tearlier `--gpg-sign`.\n \n---always::\n+--allow-empty::\n \tApply patches of commits with detailed commit messages,\n \teven if they emit no changes. (see linkgit:git-format-patch[1])\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex d11efc16f92..a8661098acf 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -124,7 +124,7 @@ struct am_state {\n \tint ignore_date;\n \tint allow_rerere_autoupdate;\n \tconst char *sign_commit;\n-\tint always;\n+\tint allow_empty;\n \tint empty_commit;\n \tint rebasing;\n };\n@@ -1251,7 +1251,7 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t}\n \n \tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tif (state->always) {\n+\t\tif (state->allow_empty) {\n \t\t\tstate->empty_commit = 1;\n \t\t} else {\n \t\t\tprintf_ln(_(\"Patch is empty.\"));\n@@ -2367,8 +2367,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n-\t\tOPT_BOOL(0, \"always\", &state.always,\n-\t\t\tN_(\"always apply patch event if the patch is empty\")),\n+\t\tOPT_BOOL(0, \"allow-empty\", &state.allow_empty,\n+\t\t\tN_(\"allow to apply patches of empty commits\")),\n \t\tOPT_HIDDEN_BOOL(0, \"empty-commit\", &state.empty_commit,\n \t\t\tN_(\"(internal use for skipping git-apply to empty commits)\")),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 364c61ba198..b47a0fa41e7 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1158,15 +1158,15 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n-test_expect_success 'am a real empty patch with the --always option' '\n+test_expect_success 'am a real empty patch with the --allow-empty option' '\n \trm -fr .git/rebase-apply &&\n \tgit reset --hard &&\n-\ttest_must_fail git am --always empty.patch 2>actual &&\n+\ttest_must_fail git am --allow-empty empty.patch 2>actual &&\n \techo Patch format detection failed. >expected &&\n \ttest_cmp expected actual\n '\n \n-test_expect_success 'am a patch of empty commits without the --always option' '\n+test_expect_success 'am a patch of empty commits without the --allow-empty option' '\n \tgrep \"empty commit\" empty-commit.patch &&\n \trm -fr .git/rebase-apply &&\n \tgit reset --hard &&\n@@ -1176,12 +1176,12 @@ test_expect_success 'am a patch of empty commits without the --always option' '\n \ttest_i18ngrep \"Patch is empty.\" err\n '\n \n-test_expect_success 'am a patch of empty commits with the --always option' '\n+test_expect_success 'am a patch of empty commits with the --allow-empty option' '\n \tgrep \"empty commit\" empty-commit.patch &&\n \trm -fr .git/rebase-apply &&\n \tgit reset --hard &&\n \tgit checkout empty-commit^ &&\n-\tgit am --always empty-commit.patch &&\n+\tgit am --allow-empty empty-commit.patch &&\n \ttest_path_is_missing .git/rebase-apply &&\n \tgit cat-file commit HEAD >actual &&\n \ttest_i18ngrep \"empty commit\" actual\n-- \ngitgitgadget\n"},{"id":"440968","messageId":"tencent_EEC1D953A204CC69ED49D8471A89110F3605@qq.com","threadId":"56877","inReplyTo":"6da5e984-1dcf-4c55-976c-49ffb8d128bf@web.de","subject":"RE: [PATCH 0/2] am: support --always option to am empty commits","fromName":"Aleen","fromEmail":"aleen42@vip.qq.com","sentAt":"2021-11-12T06:42:00Z","receivedAt":"2021-11-12T06:57:18Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Dears René,\n\n> The symmetry is compelling, but \"always\" is quite generic.  I can see\n> e.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n\n> git commit and cherry-pick have --allow-empty, which is (a bit) more\n> specific.  That seems to me a better option name to copy for a commit-\n> creating command like git am.\n\nIt was designed corresponding to --always option in git-format-patch, which\nwill be pssed into git-diff-tree. As a commit-creating command, --allow-empty\nis apparently a better choice. I will re-submit it, thank you."},{"id":"440971","messageId":"40a6024f.23b9.17d12fb977f.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqy25tj2fi.fsf@gitster.g","subject":"Re: [PATCH 0/2] am: support --always option to am empty commits","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-12T07:10:17Z","receivedAt":"2021-11-12T07:17:13Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"Dears Junio C Hamano:\n\n> René Scharfe <l.s.r@web.de> writes:\n> \n> > Am 12.11.21 um 05:58 schrieb Aleen via GitGitGadget:\n> >> Since that git has supported the --always option for the git-format-patch\n> >> command to create a patch with empty commit message, git-am should support\n> >> applying and committing with empty patches.\n> >\n> > The symmetry is compelling, but \"always\" is quite generic.  I can see\n> > e.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n> \n> What symmetry?\n> \n> > git commit and cherry-pick have --allow-empty, which is (a bit) more\n> > specific.  That seems to me a better option name to copy for a commit-\n> > creating command like git am.\n> \n> That one I can believe, even though I do not necessarily think it is\n> a good idea to add such an option.\n\nIn some cases when we need to migrate commits from one branch to another branch,\nfrom one repo to another repo, it should be necessary.\n\nAleen"},{"id":"440984","messageId":"ca9baf51-28ca-048d-ded6-f4834874f7c3@web.de","threadId":"56877","inReplyTo":"xmqqy25tj2fi.fsf@gitster.g","subject":"Re: [PATCH 0/2] am: support --always option to am empty commits","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2021-11-12T15:28:30Z","receivedAt":"2021-11-12T15:28:41Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 12.11.21 um 07:47 schrieb Junio C Hamano:\n> René Scharfe <l.s.r@web.de> writes:\n>\n>> Am 12.11.21 um 05:58 schrieb Aleen via GitGitGadget:\n>>> Since that git has supported the --always option for the git-format-patch\n>>> command to create a patch with empty commit message, git-am should support\n>>> applying and committing with empty patches.\n>>\n>> The symmetry is compelling, but \"always\" is quite generic.  I can see\n>> e.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n>\n> What symmetry?\n\nTo have the same option in both producer and consumer.\n\nRené\n"},{"id":"440987","messageId":"xmqqmtm9tkzz.fsf@gitster.g","threadId":"56877","inReplyTo":"ca9baf51-28ca-048d-ded6-f4834874f7c3@web.de","subject":"Re: [PATCH 0/2] am: support --always option to am empty commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-12T16:08:16Z","receivedAt":"2021-11-12T16:08:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n>>> The symmetry is compelling, but \"always\" is quite generic.  I can see\n>>> e.g. someone expecting \"git am --always\" to imply --keep-non-patch.\n>>\n>> What symmetry?\n>\n> To have the same option in both producer and consumer.\n\nWell, \"--always\" that stands for \"--always-show-header\" seeps\nthrough to the command line parser from revision.c layer but that is\nby accident, and is not even documented.  We may want to file it as\na bug and fix it later.\n\nAs you mentioned, allow-empty-commit does sound like a much better\nname.\n\n\n"},{"id":"441022","messageId":"xmqqpmr5ow7a.fsf@gitster.g","threadId":"56877","inReplyTo":"71e6989375c9b8dd00151481e0bb19c991e673f3.1636700040.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/4] doc: git-format-patch: specify the option --always","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-12T22:17:29Z","receivedAt":"2021-11-12T22:17:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n> Subject: Re: [PATCH v2 1/4] doc: git-format-patch: specify the option --always\n\n\"specify\" -> \"describe\", perhaps?\n\n> Signed-off-by: Aleen <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-format-patch.txt | 5 +++++\n>  1 file changed, 5 insertions(+)\n>\n> diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\n> index 113eabc107c..a9f2bf94182 100644\n> --- a/Documentation/git-format-patch.txt\n> +++ b/Documentation/git-format-patch.txt\n> @@ -30,6 +30,7 @@ SYNOPSIS\n>  \t\t   [--range-diff=<previous> [--creation-factor=<percent>]]\n>  \t\t   [--filename-max-length=<n>]\n>  \t\t   [--progress]\n> +\t\t   [--always]\n>  \t\t   [<common diff options>]\n>  \t\t   [ <since> | <revision range> ]\n>  \n> @@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.\n>  --progress::\n>  \tShow progress reports on stderr as patches are generated.\n>  \n> +--always::\n> +\tPatch commits with detailed commit messages,\n> +\teven if they emit no changes. (see linkgit:git-diff-tree[1])\n\nWhat does the verb \"Patch\" mean here?  It cannot be what the command\n\"patch\" does, i.e. apply a diff to working tree files, as\nformat-patch does not apply any patch.  \n\nWho guarantees that commit messages are detailed?  If I write a\ncommit that does not change anything with a one-liner message, would\nthis option make that message more detailed?  I doubt that it would\nbe the case.\n\nThis option may sit much better near the --ignore-if-in-upstream\noption.  The primary way the command is told which commits to show\nis via its argument (<since> or <revision-range>), and we do not\nhave many options to affect the selection of commits, but this one\nand --ignore-if-in-upstream are such options.  Borrowing from the\nway the description of that other option is phrased, perhaps\n\n    Include patches for commits that do not introduce any change,\n    which are omitted by default.\n\nDo not refer to 'git-diff-tree'; I do not think the reader who is\nlearning how to drive format-patch will learn anything new by\nreading that page.\n\nI have a feeling that if we were to endorse and promote this option\nby documenting (note: it was an accident and a mistake that this\noption is understood by underlying revision.c parse machinery, and\nnot a designed behaviour for format-patch to do anything to empty\ncommits), we should give users a better synonym to invoke it, but\nthat can and should be outside of this step.\n"},{"id":"441034","messageId":"xmqq4k8hovy3.fsf@gitster.g","threadId":"56877","inReplyTo":"59b1417da3754add11e72692ec11c09e486269e4.1636700040.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/4] am: support --always option to am empty commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-12T22:23:00Z","receivedAt":"2021-11-12T22:23:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n> Subject: Re: [PATCH v2 2/4] am: support --always option to am empty commits\n\nAs the inventor of \"format-patch\" and \"am\", I probably should wish\nthat \"to am\" were by now a valid verb, but no, it is not.\n\nMore importantly, when an empty patch comes, there can be many\ndifferent ways for the \"am\" command to handle it.  \"to am empty\ncommits\" does not say how the patch chooses to so and does not make\na very useful title for this commit.\n\nRight now, we error out, simply because it is an easy mistake to\nsave a non-patch e-mail to the mailbox when intending to save a\nseries of patches belonging to a topic, and the user is expected to\nsay \"git am --skip\" to skip over it when it happens.  The above\n\"Subject:\" can be read to mean that the new option instead allows\nsuch an empty message to be skipped without stopping and forcing the\nuser to say \"am --skip\", which may be a useful thing to do.  Or it\nmay mean that the new option creates an empty commit, using the\ncontents of the e-mail as the commit log message.  Does this patch\noffer both behaviour?  If so, \"to am\", even though it does not\nconvey a bit of information, might be an acceptable compromise.  If\nthe patch implements only one of the behaviours, then we should say\nso.  Either one of these two:\n\n    am: --always option skips empty patches\n    am: --always option records empty patches as empty commits\n\nAlso, I thought that the previous round saw a conclusion that --always\nis a bad name for the option.  If we are making the second round,\nlet's not start with a bad name and the \"fix the mistake\" of\nstarting with a bad name in a later step.  Just start with the final\nname from the beginning.\n\n> +--always::\n> +\tApply patches of commits with detailed commit messages,\n> +\teven if they emit no changes. (see linkgit:git-format-patch[1])\n\nAlmost the same comment as 1/4 applies to the above description.\n\n    --empty-patch=(skip|asis|die)::\n\tThe command usually errors out when seeing an input e-mail\n\tmessage that lacks a patch.  When this option is set to\n\t'skip', skip such an e-mail message without erroring out.\n\tWhen this option is set to 'asis', create an empty commit,\n\trecording the contents of the e-mail message as its log.\n\t'die' is the default.\n\n\nperhaps?  Assuming that 'skip' would make a useful addition to the\nmix in the future.\n\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 8677ea2348a..d11efc16f92 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -124,6 +124,8 @@ struct am_state {\n>  \tint ignore_date;\n>  \tint allow_rerere_autoupdate;\n>  \tconst char *sign_commit;\n> +\tint always;\n\nOK, so here is where the parse_options() records the command line\noption.\n\n> +\tint empty_commit;\n\nI do not think this addtion of empty_commit member to this structure\nis welcome or necessary.\n\n>  \tint rebasing;\n>  };\n\n> @@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)\n>  \t}\n\n>  \tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> -\t\tprintf_ln(_(\"Patch is empty.\"));\n> -\t\tdie_user_resolve(state);\n> +\t\tif (state->always) {\n> +\t\t\tstate->empty_commit = 1;\n> +\t\t} else {\n> +\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\tdie_user_resolve(state);\n> +\t\t}\n>  \t}\n\nI am only thinking aloud, but I suspect that the whole \"if 'patch'\nis empty, do something special\" code logically belongs to the\ncaller.  Perhaps we should remove this block altogether and let the\ncode continue the rest of this function.  And return 0, as this is\nnot like mail-system-internal-data that we want to pretend did not\neven exist, and have the caller check if \"patch\" file is empty and\nact accordingly.\n\n> @@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)\n>  \t\tif (state->interactive && do_interactive(state))\n>  \t\t\tgoto next;\n>  \n> +\t\tif (state->empty_commit)\n> +\t\t\tgoto commit;\n> +\n\nThis is probably a wrong place to jump from.  You are bypassing\napplypatch-msg-hook that may be serving as a gate to catch typos\nif you are going to create a commit.\n\nSo, perhaps check if \"patch\" is empty here, using the code you'd\nlift from parse_mail(), and if it is empty then:\n\n  - if --empty-commit is set to die (or left default), do the\n    printf_ln(_(\"Patch is empty.\")) followed by a call to\n    die_user_resolve(state), just like before.\n\n  - if it is set to skip, jump to \"next\", just like when\n    parse_mail() returned 1.\n\n  - otherwise (i.e. you are told to create an empty commit),\n    remember the fact that current e-mail has no patch, but continue\n    to the next step to run the hook.\n\n>  \t\tif (run_applypatch_msg_hook(state))\n>  \t\t\texit(1);\n\nAnd after passing the hook, if your earlier check says that there is\nno patch and you are to create an empty commit, jump to \"commit\"\nlabel from here.\n\n> diff --git a/t/t4150-am.sh b/t/t4150-am.sh\n> index 2aaaa0d7ded..5b3617857a8 100755\n> --- a/t/t4150-am.sh\n> +++ b/t/t4150-am.sh\n> @@ -196,6 +196,12 @@ test_expect_success setup '\n>  \n>  \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n>  \n> +\tgit checkout -b empty-commit &&\n> +\tgit commit -m \"empty commit\" --allow-empty &&\n> +\n> +\tgit format-patch --stdout empty-commit^ >empty.patch &&\n> +\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n> +\n>  \t# reset time\n>  \tsane_unset test_tick &&\n>  \ttest_tick\n> @@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '\n>  \tgit -C client am ../patch\n>  '\n>  \n> +test_expect_success 'am a real empty patch with the --always option' '\n> +\trm -fr .git/rebase-apply &&\n\nWhat is this one about?  If this is trying to clean up the cruft the\nprevious step made, it may be better to do the clean-up in the\nprevious step using test_when_finished.\n\n> +\tgit reset --hard &&\n> +\ttest_must_fail git am --always empty.patch 2>actual &&\n> +\techo Patch format detection failed. >expected &&\n> +\ttest_cmp expected actual\n> +'\n\nIt is curious that the error message the patch touched said \"Patch\nis empty.\" but the test checks for a different message.  Are we\ntesting the right failure mode?\n\n> +test_expect_success 'am a patch with empty commits' '\n> +\tgrep \"empty commit\" empty-commit.patch &&\n\nWhat is this testing?  If it is checking the sanity of test data we\ncreated earlier, shouldn't we do so where we generated the data\n(i.e. the \"setup\" block that we earlier saw)?\n\n> +\trm -fr .git/rebase-apply &&\n> +\tgit reset --hard &&\n\nThese are trying to clean up the cruft the previous step (added by\nthis patch) may have left.  Perhaps these should be done inside\ntest_when_finished of the previous step?\n\n> +\tgit checkout empty-commit^ &&\n> +\tgit am --always empty-commit.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n\nWe should trust \"git am\"'s exit status here, I would think, rather\nthan be so intimate with the internal implementation detail like the\nname of the temporary directory the command uses.\n\n> +\tgit cat-file commit HEAD >actual &&\n> +\ttest_i18ngrep \"empty commit\" actual\n\ntest_i18ngrep -> grep\n\nThe input (i.e. the commit that resulted in this empty patch) said.\n\"empty commit\", and we are making sure that string appears, but we\nare not making sure that is the only string appears in the log\nmessage.  Is it because we will later enhance the command to\nautomatically extend the single-liner \"empty patch\" log message into\na lot more detailed one?  I doubt it ;-)\n\nMore importantly, the above checks if (part of) the log message is\nrecorded, but does not check if the resulting commit is what is\nexpected, i.e. an empty one.\n\nPerhaps checking with \"grep\" is way too loose a test.  Shouldn't we\ndo something like\n\n\tgit show -1 --format='%B' >actual\n\nand compare it with expected \"the log is recorded as-is, and there\nis no change between HEAD^ and HEAD\"?\n\n> +'\n> +\n>  test_done\n\nThanks.\n"},{"id":"441127","messageId":"pull.1076.v3.git.1636972769.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v2.git.1636700040.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-15T10:39:27Z","receivedAt":"2021-11-15T10:39:36Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option\n 2. rename the --always option to --allow-empty\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty-commit option to handle empty patches\n\n Documentation/git-am.txt           |  9 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 48 ++++++++++++++++++++++---\n t/t4150-am.sh                      | 58 ++++++++++++++++++++++++++++++\n 4 files changed, 115 insertions(+), 6 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v2:\n\n 1:  71e6989375c ! 1:  9f1b3dd6d0b doc: git-format-patch: specify the option --always\n     @@ Metadata\n      Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n     -    doc: git-format-patch: specify the option --always\n     +    doc: git-format-patch: describe the option --always\n      \n          Signed-off-by: Aleen <aleen42@vip.qq.com>\n      \n       ## Documentation/git-format-patch.txt ##\n      @@ Documentation/git-format-patch.txt: SYNOPSIS\n     - \t\t   [--range-diff=<previous> [--creation-factor=<percent>]]\n     - \t\t   [--filename-max-length=<n>]\n     - \t\t   [--progress]\n     -+\t\t   [--always]\n     - \t\t   [<common diff options>]\n     - \t\t   [ <since> | <revision range> ]\n     - \n     -@@ Documentation/git-format-patch.txt: you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.\n     - --progress::\n     - \tShow progress reports on stderr as patches are generated.\n     + \t\t   [-n | --numbered | -N | --no-numbered]\n     + \t\t   [--start-number <n>] [--numbered-files]\n     + \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n     +-\t\t   [--ignore-if-in-upstream]\n     ++\t\t   [--ignore-if-in-upstream] [--always]\n     + \t\t   [--cover-from-description=<mode>]\n     + \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n     + \t\t   [(--reroll-count|-v) <n>]\n     +@@ Documentation/git-format-patch.txt: will want to ensure that threading is disabled for `git send-email`.\n     + \tpatches being generated, and any patch that matches is\n     + \tignored.\n       \n      +--always::\n     -+\tPatch commits with detailed commit messages,\n     -+\teven if they emit no changes. (see linkgit:git-diff-tree[1])\n     ++\tInclude patches for commits that do not introduce any change,\n     ++\twhich are omitted by default.\n      +\n     - CONFIGURATION\n     - -------------\n     - You can specify extra mail header lines to be added to each message,\n     + --cover-from-description=<mode>::\n     + \tControls which parts of the cover letter will be automatically\n     + \tpopulated using the branch's description.\n 2:  59b1417da37 ! 2:  ef33ce8c6f9 am: support --always option to am empty commits\n     @@ Metadata\n      Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n     -    am: support --always option to am empty commits\n     +    am: support --empty-commit option to handle empty patches\n      \n          Signed-off-by: Aleen <aleen42@vip.qq.com>\n      \n     @@ Documentation/git-am.txt: SYNOPSIS\n       \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n       \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n       \t [--quoted-cr=<action>]\n     -+\t [--always]\n     ++\t [--empty-commit=(die|skip|asis)]\n       \t [(<mbox> | <Maildir>)...]\n       'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n       \n     -@@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n     - \tcountermand both `commit.gpgSign` configuration variable, and\n     - \tearlier `--gpg-sign`.\n     +@@ Documentation/git-am.txt: OPTIONS\n     + --quoted-cr=<action>::\n     + \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n     -+--always::\n     -+\tApply patches of commits with detailed commit messages,\n     -+\teven if they emit no changes. (see linkgit:git-format-patch[1])\n     ++--empty-commit=(die|skip|asis)::\n     ++\tThe command usually errors out when seeing an input e-mail\n     ++\tmessage that lacks a patch. When this option is set to\n     ++\t'skip', skip such an e-mail message without outputting error.\n     ++\tWhen this option is set to 'asis', create an empty commit,\n     ++\trecording the contents of the e-mail message as its log.\n     ++\t'die' is specified by default.\n      +\n     - --continue::\n     - -r::\n     - --resolved::\n     + -m::\n     + --message-id::\n     + \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\n      \n       ## builtin/am.c ##\n     -@@ builtin/am.c: struct am_state {\n     - \tint ignore_date;\n     - \tint allow_rerere_autoupdate;\n     - \tconst char *sign_commit;\n     -+\tint always;\n     -+\tint empty_commit;\n     - \tint rebasing;\n     +@@ builtin/am.c: enum show_patch_type {\n     + \tSHOW_PATCH_DIFF = 1,\n       };\n       \n     ++enum empty_commit_action {\n     ++\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n     ++\tSKIP_EMPTY_COMMIT,     /* skip without outputting errors */\n     ++\tASIS_EMPTY_COMMIT      /* keep recording as empty commits */\n     ++};\n     ++\n     + struct am_state {\n     + \t/* state directory path */\n     + \tchar *dir;\n     +@@ builtin/am.c: struct am_state {\n     + \tint message_id;\n     + \tint scissors; /* enum scissors_type */\n     + \tint quoted_cr; /* enum quoted_cr_action */\n     ++\tint empty_commit; /* enum empty_commit_action */\n     + \tstruct strvec git_apply_opts;\n     + \tconst char *resolvemsg;\n     + \tint committer_date_is_author_date;\n     +@@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,\n     + \treturn 0;\n     + }\n     + \n     ++static int am_option_parse_empty_commit(const struct option *opt,\n     ++\t\t\t\t     const char *arg, int unset)\n     ++{\n     ++\tint *opt_value = opt->value;\n     ++\n     ++\tif (unset || !strcmp(arg, \"die\"))\n     ++\t\t*opt_value = DIE_EMPTY_COMMIT;\n     ++\telse if (!strcmp(arg, \"skip\"))\n     ++\t\t*opt_value = SKIP_EMPTY_COMMIT;\n     ++\telse if (!strcmp(arg, \"asis\"))\n     ++\t\t*opt_value = ASIS_EMPTY_COMMIT;\n     ++\telse\n     ++\t\treturn error(_(\"Invalid value for --empty-commit: %s\"), arg);\n     ++\n     ++\treturn 0;\n     ++}\n     ++\n     + /**\n     +  * Returns path relative to the am_state directory.\n     +  */\n      @@ builtin/am.c: static int parse_mail(struct am_state *state, const char *mail)\n     + \t\tgoto finish;\n       \t}\n       \n     - \tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n     +-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n      -\t\tprintf_ln(_(\"Patch is empty.\"));\n      -\t\tdie_user_resolve(state);\n     -+\t\tif (state->always) {\n     -+\t\t\tstate->empty_commit = 1;\n     -+\t\t} else {\n     -+\t\t\tprintf_ln(_(\"Patch is empty.\"));\n     -+\t\t\tdie_user_resolve(state);\n     -+\t\t}\n     - \t}\n     - \n     +-\t}\n     +-\n       \tstrbuf_addstr(&msg, \"\\n\\n\");\n     + \tstrbuf_addbuf(&msg, &mi.log_message);\n     + \tstrbuf_stripspace(&msg, 0);\n      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n       \t\tif (state->interactive && do_interactive(state))\n       \t\t\tgoto next;\n       \n     -+\t\tif (state->empty_commit)\n     -+\t\t\tgoto commit;\n     ++\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n     ++\t\t\tif (state->empty_commit == SKIP_EMPTY_COMMIT)\n     ++\t\t\t\tgoto next;\n     ++\t\t\telse if (state->empty_commit == ASIS_EMPTY_COMMIT) {\n     ++\t\t\t\tif (run_applypatch_msg_hook(state))\n     ++\t\t\t\t\texit(1);\n     ++\t\t\t\telse\n     ++\t\t\t\t\tgoto commit;\n     ++\t\t\t} else if (state->empty_commit == DIE_EMPTY_COMMIT) {\n     ++\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n     ++\t\t\t\tdie_user_resolve(state);\n     ++\t\t\t}\n     ++\t\t}\n      +\n       \t\tif (run_applypatch_msg_hook(state))\n       \t\t\texit(1);\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n       \t\t  N_(\"GPG-sign commits\"),\n       \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n     -+\t\tOPT_BOOL(0, \"always\", &state.always,\n     -+\t\t\tN_(\"always apply patch event if the patch is empty\")),\n     -+\t\tOPT_HIDDEN_BOOL(0, \"empty-commit\", &state.empty_commit,\n     -+\t\t\tN_(\"(internal use for skipping git-apply to empty commits)\")),\n     ++\t\t{ OPTION_CALLBACK, 0, \"empty-commit\", &state.empty_commit,\n     ++\t\t  \"(die|skip|asis)\",\n     ++\t\t  N_(\"specify how to handle empty patches\"),\n     ++\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n       \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n       \t\t\tN_(\"(internal use for git-rebase)\")),\n       \t\tOPT_END()\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n       \tgit -C client am ../patch\n       '\n       \n     -+test_expect_success 'am a real empty patch with the --always option' '\n     -+\trm -fr .git/rebase-apply &&\n     -+\tgit reset --hard &&\n     -+\ttest_must_fail git am --always empty.patch 2>actual &&\n     ++test_expect_success 'still output error with --empty-commit when meeting empty files' '\n     ++\ttest_must_fail git am --empty-commit=skip empty.patch 2>actual &&\n      +\techo Patch format detection failed. >expected &&\n      +\ttest_cmp expected actual\n      +'\n      +\n     -+test_expect_success 'am a patch with empty commits' '\n     -+\tgrep \"empty commit\" empty-commit.patch &&\n     -+\trm -fr .git/rebase-apply &&\n     -+\tgit reset --hard &&\n     ++test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n      +\tgit checkout empty-commit^ &&\n     -+\tgit am --always empty-commit.patch &&\n     ++\ttest_must_fail git am empty-commit.patch >err &&\n     ++\ttest_path_is_dir .git/rebase-apply &&\n     ++\ttest_i18ngrep \"Patch is empty.\" err &&\n     ++\trm -fr .git/rebase-apply &&\n     ++\n     ++\ttest_must_fail git am --empty-commit=die empty-commit.patch >err &&\n     ++\ttest_path_is_dir .git/rebase-apply &&\n     ++\ttest_i18ngrep \"Patch is empty.\" err &&\n     ++\trm -fr .git/rebase-apply &&\n     ++\n     ++\ttest_must_fail git am --empty-commit=die cover-letter.patch >err &&\n     ++\ttest_path_is_dir .git/rebase-apply &&\n     ++\ttest_i18ngrep \"Patch is empty.\" err &&\n     ++\trm -fr .git/rebase-apply\n     ++'\n     ++\n     ++test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n     ++\tgit am --empty-commit=skip empty-commit.patch >err &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n     ++\tgit rev-parse empty-commit^ >expected &&\n     ++\tgit rev-parse HEAD >actual &&\n     ++\ttest_cmp expected actual &&\n     ++\n     ++\tgit am --empty-commit=skip cover-letter.patch >err &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n     ++\tgit rev-parse empty-commit^ >expected &&\n     ++\tgit rev-parse HEAD >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n     ++\tgit am --empty-commit=asis empty-commit.patch &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n     ++\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgrep -f actual expected &&\n     ++\n     ++\tgit am --empty-commit=asis cover-letter.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit cat-file commit HEAD >actual &&\n     -+\ttest_i18ngrep \"empty commit\" actual\n     ++\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgrep -f actual expected\n      +'\n      +\n       test_done\n 3:  da024ced668 < -:  ----------- test: am: add the case when not passing the --always option\n 4:  45e9720f40b < -:  ----------- chore: am: rename the --always option to --allow-empty\n\n-- \ngitgitgadget\n"},{"id":"441128","messageId":"9f1b3dd6d0b2d3f22fe960b0c3aaa3d888d26c88.1636972769.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v3.git.1636972769.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-15T10:39:28Z","receivedAt":"2021-11-15T10:39:47Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441129","messageId":"ef33ce8c6f9e1e1519d3908edd3491fd00d78ac7.1636972769.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v3.git.1636972769.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] am: support --empty-commit option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-15T10:39:29Z","receivedAt":"2021-11-15T10:39:51Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 48 +++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 58 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 110 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..d8d3bf202d7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty-commit=(die|skip|asis)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty-commit=(die|skip|asis)::\n+\tThe command usually errors out when seeing an input e-mail\n+\tmessage that lacks a patch. When this option is set to\n+\t'skip', skip such an e-mail message without outputting error.\n+\tWhen this option is set to 'asis', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\t'die' is specified by default.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..e7755c1377e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_commit_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tSKIP_EMPTY_COMMIT,     /* skip without outputting errors */\n+\tASIS_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_commit; /* enum empty_commit_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty_commit(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tif (unset || !strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"skip\"))\n+\t\t*opt_value = SKIP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"asis\"))\n+\t\t*opt_value = ASIS_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty-commit: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tif (state->empty_commit == SKIP_EMPTY_COMMIT)\n+\t\t\t\tgoto next;\n+\t\t\telse if (state->empty_commit == ASIS_EMPTY_COMMIT) {\n+\t\t\t\tif (run_applypatch_msg_hook(state))\n+\t\t\t\t\texit(1);\n+\t\t\t\telse\n+\t\t\t\t\tgoto commit;\n+\t\t\t} else if (state->empty_commit == DIE_EMPTY_COMMIT) {\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\t{ OPTION_CALLBACK, 0, \"empty-commit\", &state.empty_commit,\n+\t\t  \"(die|skip|asis)\",\n+\t\t  N_(\"specify how to handle empty patches\"),\n+\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..13fdf2f86c6 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,56 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'still output error with --empty-commit when meeting empty files' '\n+\ttest_must_fail git am --empty-commit=skip empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty-commit=die empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty-commit=die cover-letter.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n+\tgit am --empty-commit=skip empty-commit.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tgit am --empty-commit=skip cover-letter.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty-commit=asis empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected &&\n+\n+\tgit am --empty-commit=asis cover-letter.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441131","messageId":"60d0c28e.2943.17d234d975d.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"ef33ce8c6f9e1e1519d3908edd3491fd00d78ac7.1636972769.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/2] am: support --empty-commit option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-15T11:13:47Z","receivedAt":"2021-11-15T11:14:52Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"Dears Hamano,\n\nSorry about the late submission, and I have modified it to support the `--empty-commit=(die|skip|asis)` pattern.\n\n> +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty-commit=asis empty-commit.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit show empty-commit --format=\"%B\" >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\tgrep -f actual expected &&\n> +\n> +\tgit am --empty-commit=asis cover-letter.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit show empty-commit --format=\"%B\" >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\tgrep -f actual expected\n> +'\n\nWhen it comes to the last test case, there are some accidental contents recognized as commit messages inside an empty patch, like:\n\n1. version signatures:\n  > -- \n  > 2.34.0.rc2.390.gef33ce8c6f\n2. information of cover letter:\n  > *** BLURB HERE ***\n  >\n  > A U Thor (1):\n  >   empty commit\n\nI don't think it is necessary to fix it within `mailinfo.c`. Can you give me some suggestions on whether this case should be handled?\n\nAleen"},{"id":"441251","messageId":"pull.1076.v4.git.1637039888.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v3.git.1636972769.gitgitgadget@gmail.com","subject":"[PATCH v4 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-16T05:18:06Z","receivedAt":"2021-11-16T05:54:04Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option\n 2. rename the --always option to --allow-empty\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases\n 2. grep -f cannot be used under Mac OS\n\n----------------------------------------------------------------------------\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty-commit option to handle empty patches\n\n Documentation/git-am.txt           |  9 ++++\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 48 ++++++++++++++++++--\n t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++++++\n 4 files changed, 130 insertions(+), 6 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v3:\n\n 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always\n 2:  ef33ce8c6f9 ! 2:  b7e30c9b7ab am: support --empty-commit option to handle empty patches\n     @@ t/t4150-am.sh: test_expect_success setup '\n      +\tgit commit -m \"empty commit\" --allow-empty &&\n      +\n      +\tgit format-patch --stdout empty-commit^ >empty.patch &&\n     ++\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n      +\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n      +\n       \t# reset time\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +\n      +\tgit am --empty-commit=skip cover-letter.patch >err &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit rev-parse empty-commit^ >expected &&\n     -+\tgit rev-parse HEAD >actual &&\n     -+\ttest_cmp expected actual\n     ++\ttest_cmp_rev empty-commit^ HEAD\n      +'\n      +\n      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n      +\tgit am --empty-commit=asis empty-commit.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\t{\n     ++\t\tgit show empty-commit --format=\"%B\" &&\n     ++\t\techo \"--\" &&\n     ++\t\tgit version | sed -e \"s/^git version //\" &&\n     ++\t\techo\n     ++\t} >expected &&\n      +\tgit show HEAD --format=\"%B\" >actual &&\n     -+\tgrep -f actual expected &&\n     ++\ttest_cmp actual expected &&\n      +\n      +\tgit am --empty-commit=asis cover-letter.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\t{\n     ++\t\techo \"*** SUBJECT HERE ***\" &&\n     ++\t\techo &&\n     ++\t\techo \"*** BLURB HERE ***\" &&\n     ++\t\techo &&\n     ++\t\techo \"A U Thor (1):\" &&\n     ++\t\tprintf \"  \" &&\n     ++\t\tgit show empty-commit --format=\"%B\" &&\n     ++\t\techo \"--\" &&\n     ++\t\tgit version | sed -e \"s/^git version //\" &&\n     ++\t\techo\n     ++\t} >expected &&\n      +\tgit show HEAD --format=\"%B\" >actual &&\n     -+\tgrep -f actual expected\n     ++\ttest_cmp actual expected\n      +'\n      +\n       test_done\n\n-- \ngitgitgadget\n"},{"id":"441252","messageId":"9f1b3dd6d0b2d3f22fe960b0c3aaa3d888d26c88.1637039888.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v4.git.1637039888.gitgitgadget@gmail.com","subject":"[PATCH v4 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-16T05:18:07Z","receivedAt":"2021-11-16T05:54:18Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441253","messageId":"b7e30c9b7abecdc871ddc38122ca042e940cb190.1637039888.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v4.git.1637039888.gitgitgadget@gmail.com","subject":"[PATCH v4 2/2] am: support --empty-commit option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-16T05:18:08Z","receivedAt":"2021-11-16T05:54:21Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++\n builtin/am.c             | 48 +++++++++++++++++++++++---\n t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 125 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..d8d3bf202d7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty-commit=(die|skip|asis)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty-commit=(die|skip|asis)::\n+\tThe command usually errors out when seeing an input e-mail\n+\tmessage that lacks a patch. When this option is set to\n+\t'skip', skip such an e-mail message without outputting error.\n+\tWhen this option is set to 'asis', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\t'die' is specified by default.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..e7755c1377e 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_commit_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tSKIP_EMPTY_COMMIT,     /* skip without outputting errors */\n+\tASIS_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_commit; /* enum empty_commit_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty_commit(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tif (unset || !strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"skip\"))\n+\t\t*opt_value = SKIP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"asis\"))\n+\t\t*opt_value = ASIS_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty-commit: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tif (state->empty_commit == SKIP_EMPTY_COMMIT)\n+\t\t\t\tgoto next;\n+\t\t\telse if (state->empty_commit == ASIS_EMPTY_COMMIT) {\n+\t\t\t\tif (run_applypatch_msg_hook(state))\n+\t\t\t\t\texit(1);\n+\t\t\t\telse\n+\t\t\t\t\tgoto commit;\n+\t\t\t} else if (state->empty_commit == DIE_EMPTY_COMMIT) {\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\t{ OPTION_CALLBACK, 0, \"empty-commit\", &state.empty_commit,\n+\t\t  \"(die|skip|asis)\",\n+\t\t  N_(\"specify how to handle empty patches\"),\n+\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..e657180c201 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,13 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'still output error with --empty-commit when meeting empty files' '\n+\ttest_must_fail git am --empty-commit=skip empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty-commit=die empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty-commit=die cover-letter.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n+\tgit am --empty-commit=skip empty-commit.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tgit am --empty-commit=skip cover-letter.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\ttest_cmp_rev empty-commit^ HEAD\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty-commit=asis empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected &&\n+\n+\tgit am --empty-commit=asis cover-letter.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\techo \"*** SUBJECT HERE ***\" &&\n+\t\techo &&\n+\t\techo \"*** BLURB HERE ***\" &&\n+\t\techo &&\n+\t\techo \"A U Thor (1):\" &&\n+\t\tprintf \"  \" &&\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441289","messageId":"23de56a6-5b41-d8be-d59c-5a4e025ccd73@gmail.com","threadId":"56877","inReplyTo":"b7e30c9b7abecdc871ddc38122ca042e940cb190.1637039888.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2021-11-16T10:07:31Z","receivedAt":"2021-11-16T10:07:54Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Aleen\n\nThanks for working on this\n\nOn 16/11/2021 05:18, Aleen via GitGitGadget wrote:\n> From: Aleen <aleen42@vip.qq.com>\n> \n> Signed-off-by: Aleen <aleen42@vip.qq.com>\n> ---\n>   Documentation/git-am.txt |  9 +++++\n>   builtin/am.c             | 48 +++++++++++++++++++++++---\n>   t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++\n>   3 files changed, 125 insertions(+), 5 deletions(-)\n> \n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index 0a4a984dfde..d8d3bf202d7 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -16,6 +16,7 @@ SYNOPSIS\n>   \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n>   \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n>   \t [--quoted-cr=<action>]\n> +\t [--empty-commit=(die|skip|asis)]\n>   \t [(<mbox> | <Maildir>)...]\n>   'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n>   \n> @@ -63,6 +64,14 @@ OPTIONS\n>   --quoted-cr=<action>::\n>   \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n>   \n> +--empty-commit=(die|skip|asis)::\n> +\tThe command usually errors out when seeing an input e-mail\n> +\tmessage that lacks a patch. When this option is set to\n> +\t'skip', skip such an e-mail message without outputting error.\n> +\tWhen this option is set to 'asis', create an empty commit,\n> +\trecording the contents of the e-mail message as its log.\n> +\t'die' is specified by default.\n\nThis feels sufficiently similar to the case of handling empty commits in \n'git rebase' that it is worth trying to have a similar user interface. \nOtherwise the two commands have two different option names doing more or \nless the same thing. 'git rebase' has --empty=[drop,keep,ask] where drop \nis the default. If am were to accept --empty=[drop,keep,die] it would \noffer a similar user experience.\n\nBest Wishes\n\nPhillip\n\n>   -m::\n>   --message-id::\n>   \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 8677ea2348a..e7755c1377e 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -87,6 +87,12 @@ enum show_patch_type {\n>   \tSHOW_PATCH_DIFF = 1,\n>   };\n>   \n> +enum empty_commit_action {\n> +\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n> +\tSKIP_EMPTY_COMMIT,     /* skip without outputting errors */\n> +\tASIS_EMPTY_COMMIT      /* keep recording as empty commits */\n> +};\n> +\n>   struct am_state {\n>   \t/* state directory path */\n>   \tchar *dir;\n> @@ -118,6 +124,7 @@ struct am_state {\n>   \tint message_id;\n>   \tint scissors; /* enum scissors_type */\n>   \tint quoted_cr; /* enum quoted_cr_action */\n> +\tint empty_commit; /* enum empty_commit_action */\n>   \tstruct strvec git_apply_opts;\n>   \tconst char *resolvemsg;\n>   \tint committer_date_is_author_date;\n> @@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n>   \treturn 0;\n>   }\n>   \n> +static int am_option_parse_empty_commit(const struct option *opt,\n> +\t\t\t\t     const char *arg, int unset)\n> +{\n> +\tint *opt_value = opt->value;\n> +\n> +\tif (unset || !strcmp(arg, \"die\"))\n> +\t\t*opt_value = DIE_EMPTY_COMMIT;\n> +\telse if (!strcmp(arg, \"skip\"))\n> +\t\t*opt_value = SKIP_EMPTY_COMMIT;\n> +\telse if (!strcmp(arg, \"asis\"))\n> +\t\t*opt_value = ASIS_EMPTY_COMMIT;\n> +\telse\n> +\t\treturn error(_(\"Invalid value for --empty-commit: %s\"), arg);\n> +\n> +\treturn 0;\n> +}\n> +\n>   /**\n>    * Returns path relative to the am_state directory.\n>    */\n> @@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n>   \t\tgoto finish;\n>   \t}\n>   \n> -\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> -\t\tprintf_ln(_(\"Patch is empty.\"));\n> -\t\tdie_user_resolve(state);\n> -\t}\n> -\n>   \tstrbuf_addstr(&msg, \"\\n\\n\");\n>   \tstrbuf_addbuf(&msg, &mi.log_message);\n>   \tstrbuf_stripspace(&msg, 0);\n> @@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n>   \t\tif (state->interactive && do_interactive(state))\n>   \t\t\tgoto next;\n>   \n> +\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> +\t\t\tif (state->empty_commit == SKIP_EMPTY_COMMIT)\n> +\t\t\t\tgoto next;\n> +\t\t\telse if (state->empty_commit == ASIS_EMPTY_COMMIT) {\n> +\t\t\t\tif (run_applypatch_msg_hook(state))\n> +\t\t\t\t\texit(1);\n> +\t\t\t\telse\n> +\t\t\t\t\tgoto commit;\n> +\t\t\t} else if (state->empty_commit == DIE_EMPTY_COMMIT) {\n> +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\t\tdie_user_resolve(state);\n> +\t\t\t}\n> +\t\t}\n> +\n>   \t\tif (run_applypatch_msg_hook(state))\n>   \t\t\texit(1);\n>   \n> @@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)\n>   \t\t\tdie_user_resolve(state);\n>   \t\t}\n>   \n> +commit:\n>   \t\tdo_commit(state);\n>   \n>   next:\n> @@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>   \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n>   \t\t  N_(\"GPG-sign commits\"),\n>   \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n> +\t\t{ OPTION_CALLBACK, 0, \"empty-commit\", &state.empty_commit,\n> +\t\t  \"(die|skip|asis)\",\n> +\t\t  N_(\"specify how to handle empty patches\"),\n> +\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n>   \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n>   \t\t\tN_(\"(internal use for git-rebase)\")),\n>   \t\tOPT_END()\n> diff --git a/t/t4150-am.sh b/t/t4150-am.sh\n> index 2aaaa0d7ded..e657180c201 100755\n> --- a/t/t4150-am.sh\n> +++ b/t/t4150-am.sh\n> @@ -196,6 +196,13 @@ test_expect_success setup '\n>   \n>   \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n>   \n> +\tgit checkout -b empty-commit &&\n> +\tgit commit -m \"empty commit\" --allow-empty &&\n> +\n> +\tgit format-patch --stdout empty-commit^ >empty.patch &&\n> +\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n> +\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n> +\n>   \t# reset time\n>   \tsane_unset test_tick &&\n>   \ttest_tick\n> @@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '\n>   \tgit -C client am ../patch\n>   '\n>   \n> +test_expect_success 'still output error with --empty-commit when meeting empty files' '\n> +\ttest_must_fail git am --empty-commit=skip empty.patch 2>actual &&\n> +\techo Patch format detection failed. >expected &&\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n> +\tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am empty-commit.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply &&\n> +\n> +\ttest_must_fail git am --empty-commit=die empty-commit.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply &&\n> +\n> +\ttest_must_fail git am --empty-commit=die cover-letter.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply\n> +'\n> +\n> +test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty-commit=skip empty-commit.patch >err &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit rev-parse empty-commit^ >expected &&\n> +\tgit rev-parse HEAD >actual &&\n> +\ttest_cmp expected actual &&\n> +\n> +\tgit am --empty-commit=skip cover-letter.patch >err &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\ttest_cmp_rev empty-commit^ HEAD\n> +'\n> +\n> +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty-commit=asis empty-commit.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\t{\n> +\t\tgit show empty-commit --format=\"%B\" &&\n> +\t\techo \"--\" &&\n> +\t\tgit version | sed -e \"s/^git version //\" &&\n> +\t\techo\n> +\t} >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\ttest_cmp actual expected &&\n> +\n> +\tgit am --empty-commit=asis cover-letter.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\t{\n> +\t\techo \"*** SUBJECT HERE ***\" &&\n> +\t\techo &&\n> +\t\techo \"*** BLURB HERE ***\" &&\n> +\t\techo &&\n> +\t\techo \"A U Thor (1):\" &&\n> +\t\tprintf \"  \" &&\n> +\t\tgit show empty-commit --format=\"%B\" &&\n> +\t\techo \"--\" &&\n> +\t\tgit version | sed -e \"s/^git version //\" &&\n> +\t\techo\n> +\t} >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\ttest_cmp actual expected\n> +'\n> +\n>   test_done\n> \n"},{"id":"441291","messageId":"77268d7d.3ce.17d284d7732.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"23de56a6-5b41-d8be-d59c-5a4e025ccd73@gmail.com","subject":"Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-16T10:31:45Z","receivedAt":"2021-11-16T10:33:23Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> > +--empty-commit=(die|skip|asis)::\n> > +\tThe command usually errors out when seeing an input e-mail\n> > +\tmessage that lacks a patch. When this option is set to\n> > +\t'skip', skip such an e-mail message without outputting error.\n> > +\tWhen this option is set to 'asis', create an empty commit,\n> > +\trecording the contents of the e-mail message as its log.\n> > +\t'die' is specified by default.\n> \n> This feels sufficiently similar to the case of handling empty commits in \n> 'git rebase' that it is worth trying to have a similar user interface. \n> Otherwise the two commands have two different option names doing more or \n> less the same thing. 'git rebase' has --empty=[drop,keep,ask] where drop \n> is the default. If am were to accept --empty=[drop,keep,die] it would \n> offer a similar user experience.\n> \n> Best Wishes\n> \n> Phillip\n\nDears Phillip,\n\nIt seems a good idea. Can Hamano make a decision?\n\nAleen\n"},{"id":"441414","messageId":"xmqqee7f41n7.fsf@gitster.g","threadId":"56877","inReplyTo":"23de56a6-5b41-d8be-d59c-5a4e025ccd73@gmail.com","subject":"Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-17T08:39:08Z","receivedAt":"2021-11-17T08:39:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>>   +--empty-commit=(die|skip|asis)::\n>> +\tThe command usually errors out when seeing an input e-mail\n>> +\tmessage that lacks a patch. When this option is set to\n>> +\t'skip', skip such an e-mail message without outputting error.\n>> +\tWhen this option is set to 'asis', create an empty commit,\n>> +\trecording the contents of the e-mail message as its log.\n>> +\t'die' is specified by default.\n>\n> This feels sufficiently similar to the case of handling empty commits\n> in 'git rebase' that it is worth trying to have a similar user\n> interface. Otherwise the two commands have two different option names\n> doing more or less the same thing. 'git rebase' has\n> --empty=[drop,keep,ask] where drop is the default. If am were to\n>  accept --empty=[drop,keep,die] it would offer a similar user\n> experience.\n\nAh, thanks for noticing.  I like the three words you suggest.  If we\nalready have a similar option, we definitely should follow suit, and\nI think \"--empty\" would be a better fit than \"--empty-commit\" in the\ncontext of talking about the _input_ to \"am\".\n\n"},{"id":"441428","messageId":"pull.1076.v5.git.1637141636.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v4.git.1637039888.gitgitgadget@gmail.com","subject":"[PATCH v5 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-17T09:33:54Z","receivedAt":"2021-11-17T09:34:01Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty option to handle empty patches\n\n Documentation/git-am.txt           |  9 ++++\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 48 ++++++++++++++++++--\n t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++++++\n 4 files changed, 130 insertions(+), 6 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v4:\n\n 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always\n 2:  b7e30c9b7ab ! 2:  96d8573dc80 am: support --empty-commit option to handle empty patches\n     @@ Metadata\n      Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n     -    am: support --empty-commit option to handle empty patches\n     +    am: support --empty option to handle empty patches\n      \n          Signed-off-by: Aleen <aleen42@vip.qq.com>\n      \n     @@ Documentation/git-am.txt: SYNOPSIS\n       \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n       \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n       \t [--quoted-cr=<action>]\n     -+\t [--empty-commit=(die|skip|asis)]\n     ++\t [--empty=(die|drop|keep)]\n       \t [(<mbox> | <Maildir>)...]\n       'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n       \n     @@ Documentation/git-am.txt: OPTIONS\n       --quoted-cr=<action>::\n       \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n     -+--empty-commit=(die|skip|asis)::\n     ++--empty-commit=(die|drop|keep)::\n      +\tThe command usually errors out when seeing an input e-mail\n      +\tmessage that lacks a patch. When this option is set to\n     -+\t'skip', skip such an e-mail message without outputting error.\n     -+\tWhen this option is set to 'asis', create an empty commit,\n     ++\t'drop', skip such an e-mail message without outputting error.\n     ++\tWhen this option is set to 'keep', create an empty commit,\n      +\trecording the contents of the e-mail message as its log.\n      +\t'die' is specified by default.\n      +\n     @@ builtin/am.c: enum show_patch_type {\n       \tSHOW_PATCH_DIFF = 1,\n       };\n       \n     -+enum empty_commit_action {\n     ++enum empty_action {\n      +\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n     -+\tSKIP_EMPTY_COMMIT,     /* skip without outputting errors */\n     -+\tASIS_EMPTY_COMMIT      /* keep recording as empty commits */\n     ++\tDROP_EMPTY_COMMIT,     /* skip without outputting errors */\n     ++\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n      +};\n      +\n       struct am_state {\n     @@ builtin/am.c: struct am_state {\n       \tint message_id;\n       \tint scissors; /* enum scissors_type */\n       \tint quoted_cr; /* enum quoted_cr_action */\n     -+\tint empty_commit; /* enum empty_commit_action */\n     ++\tint empty_type; /* enum empty_action */\n       \tstruct strvec git_apply_opts;\n       \tconst char *resolvemsg;\n       \tint committer_date_is_author_date;\n     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,\n      +\n      +\tif (unset || !strcmp(arg, \"die\"))\n      +\t\t*opt_value = DIE_EMPTY_COMMIT;\n     -+\telse if (!strcmp(arg, \"skip\"))\n     -+\t\t*opt_value = SKIP_EMPTY_COMMIT;\n     -+\telse if (!strcmp(arg, \"asis\"))\n     -+\t\t*opt_value = ASIS_EMPTY_COMMIT;\n     ++\telse if (!strcmp(arg, \"drop\"))\n     ++\t\t*opt_value = DROP_EMPTY_COMMIT;\n     ++\telse if (!strcmp(arg, \"keep\"))\n     ++\t\t*opt_value = KEEP_EMPTY_COMMIT;\n      +\telse\n     -+\t\treturn error(_(\"Invalid value for --empty-commit: %s\"), arg);\n     ++\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n      +\n      +\treturn 0;\n      +}\n     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n       \t\t\tgoto next;\n       \n      +\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n     -+\t\t\tif (state->empty_commit == SKIP_EMPTY_COMMIT)\n     ++\t\t\tif (state->empty_type == DROP_EMPTY_COMMIT)\n      +\t\t\t\tgoto next;\n     -+\t\t\telse if (state->empty_commit == ASIS_EMPTY_COMMIT) {\n     ++\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n      +\t\t\t\tif (run_applypatch_msg_hook(state))\n      +\t\t\t\t\texit(1);\n      +\t\t\t\telse\n      +\t\t\t\t\tgoto commit;\n     -+\t\t\t} else if (state->empty_commit == DIE_EMPTY_COMMIT) {\n     ++\t\t\t} else if (state->empty_type == DIE_EMPTY_COMMIT) {\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tdie_user_resolve(state);\n      +\t\t\t}\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n       \t\t  N_(\"GPG-sign commits\"),\n       \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n     -+\t\t{ OPTION_CALLBACK, 0, \"empty-commit\", &state.empty_commit,\n     -+\t\t  \"(die|skip|asis)\",\n     ++\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n     ++\t\t  \"(die|drop|keep)\",\n      +\t\t  N_(\"specify how to handle empty patches\"),\n      +\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n       \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n       \tgit -C client am ../patch\n       '\n       \n     -+test_expect_success 'still output error with --empty-commit when meeting empty files' '\n     -+\ttest_must_fail git am --empty-commit=skip empty.patch 2>actual &&\n     ++test_expect_success 'still output error with --empty when meeting empty files' '\n     ++\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n      +\techo Patch format detection failed. >expected &&\n      +\ttest_cmp expected actual\n      +'\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +\ttest_i18ngrep \"Patch is empty.\" err &&\n      +\trm -fr .git/rebase-apply &&\n      +\n     -+\ttest_must_fail git am --empty-commit=die empty-commit.patch >err &&\n     ++\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n      +\ttest_path_is_dir .git/rebase-apply &&\n      +\ttest_i18ngrep \"Patch is empty.\" err &&\n      +\trm -fr .git/rebase-apply &&\n      +\n     -+\ttest_must_fail git am --empty-commit=die cover-letter.patch >err &&\n     ++\ttest_must_fail git am --empty=die cover-letter.patch >err &&\n      +\ttest_path_is_dir .git/rebase-apply &&\n      +\ttest_i18ngrep \"Patch is empty.\" err &&\n      +\trm -fr .git/rebase-apply\n      +'\n      +\n      +test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n     -+\tgit am --empty-commit=skip empty-commit.patch >err &&\n     ++\tgit am --empty=drop empty-commit.patch >err &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n      +\tgit rev-parse empty-commit^ >expected &&\n      +\tgit rev-parse HEAD >actual &&\n      +\ttest_cmp expected actual &&\n      +\n     -+\tgit am --empty-commit=skip cover-letter.patch >err &&\n     ++\tgit am --empty=drop cover-letter.patch >err &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n      +\ttest_cmp_rev empty-commit^ HEAD\n      +'\n      +\n      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n     -+\tgit am --empty-commit=asis empty-commit.patch &&\n     ++\tgit am --empty=keep empty-commit.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n      +\t{\n      +\t\tgit show empty-commit --format=\"%B\" &&\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +\tgit show HEAD --format=\"%B\" >actual &&\n      +\ttest_cmp actual expected &&\n      +\n     -+\tgit am --empty-commit=asis cover-letter.patch &&\n     ++\tgit am --empty=keep cover-letter.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n      +\t{\n      +\t\techo \"*** SUBJECT HERE ***\" &&\n\n-- \ngitgitgadget\n"},{"id":"441429","messageId":"9f1b3dd6d0b2d3f22fe960b0c3aaa3d888d26c88.1637141636.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v5.git.1637141636.gitgitgadget@gmail.com","subject":"[PATCH v5 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-17T09:33:55Z","receivedAt":"2021-11-17T09:34:02Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441430","messageId":"96d8573dc808bc32990842675ca32a0d1e8a8cef.1637141636.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v5.git.1637141636.gitgitgadget@gmail.com","subject":"[PATCH v5 2/2] am: support --empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-17T09:33:56Z","receivedAt":"2021-11-17T09:34:06Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++\n builtin/am.c             | 48 +++++++++++++++++++++++---\n t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 125 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..665bc89ca9f 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty-commit=(die|drop|keep)::\n+\tThe command usually errors out when seeing an input e-mail\n+\tmessage that lacks a patch. When this option is set to\n+\t'drop', skip such an e-mail message without outputting error.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\t'die' is specified by default.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..1a3ed87b445 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip without outputting errors */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty_commit(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tif (unset || !strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tif (state->empty_type == DROP_EMPTY_COMMIT)\n+\t\t\t\tgoto next;\n+\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n+\t\t\t\tif (run_applypatch_msg_hook(state))\n+\t\t\t\t\texit(1);\n+\t\t\t\telse\n+\t\t\t\t\tgoto commit;\n+\t\t\t} else if (state->empty_type == DIE_EMPTY_COMMIT) {\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n+\t\t  \"(die|drop|keep)\",\n+\t\t  N_(\"specify how to handle empty patches\"),\n+\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..3119556884d 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,13 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'still output error with --empty when meeting empty files' '\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty=die cover-letter.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=drop empty-commit.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tgit am --empty=drop cover-letter.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\ttest_cmp_rev empty-commit^ HEAD\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected &&\n+\n+\tgit am --empty=keep cover-letter.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\techo \"*** SUBJECT HERE ***\" &&\n+\t\techo &&\n+\t\techo \"*** BLURB HERE ***\" &&\n+\t\techo &&\n+\t\techo \"A U Thor (1):\" &&\n+\t\tprintf \"  \" &&\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441592","messageId":"9f1b3dd6d0b2d3f22fe960b0c3aaa3d888d26c88.1637232636.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","subject":"[PATCH v6 1/3] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-18T10:50:34Z","receivedAt":"2021-11-18T10:51:05Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441593","messageId":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v5.git.1637141636.gitgitgadget@gmail.com","subject":"[PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-18T10:50:33Z","receivedAt":"2021-11-18T10:51:10Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nAleen (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty option to handle empty patches\n  am: throw an error when passing --empty option without value\n\n Documentation/git-am.txt           |  9 ++++\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 49 ++++++++++++++++--\n t/t4150-am.sh                      | 79 ++++++++++++++++++++++++++++++\n 4 files changed, 137 insertions(+), 6 deletions(-)\n\n\nbase-commit: b550198c73edd4cc058832dcf74b41aeec2adba2\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v5:\n\n 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always\n 2:  96d8573dc80 = 2:  96d8573dc80 am: support --empty option to handle empty patches\n -:  ----------- > 3:  e907a2b2faa am: throw an error when passing --empty option without value\n\n-- \ngitgitgadget\n"},{"id":"441594","messageId":"96d8573dc808bc32990842675ca32a0d1e8a8cef.1637232636.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","subject":"[PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-18T10:50:35Z","receivedAt":"2021-11-18T10:51:12Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++\n builtin/am.c             | 48 +++++++++++++++++++++++---\n t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 125 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..665bc89ca9f 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty-commit=(die|drop|keep)::\n+\tThe command usually errors out when seeing an input e-mail\n+\tmessage that lacks a patch. When this option is set to\n+\t'drop', skip such an e-mail message without outputting error.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\t'die' is specified by default.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..1a3ed87b445 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip without outputting errors */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty_commit(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tif (unset || !strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tif (state->empty_type == DROP_EMPTY_COMMIT)\n+\t\t\t\tgoto next;\n+\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n+\t\t\t\tif (run_applypatch_msg_hook(state))\n+\t\t\t\t\texit(1);\n+\t\t\t\telse\n+\t\t\t\t\tgoto commit;\n+\t\t\t} else if (state->empty_type == DIE_EMPTY_COMMIT) {\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n \n@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n+\t\t  \"(die|drop|keep)\",\n+\t\t  N_(\"specify how to handle empty patches\"),\n+\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..3119556884d 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,13 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n+\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'still output error with --empty when meeting empty files' '\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply &&\n+\n+\ttest_must_fail git am --empty=die cover-letter.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\ttest_i18ngrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=drop empty-commit.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tgit am --empty=drop cover-letter.patch >err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\ttest_cmp_rev empty-commit^ HEAD\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected &&\n+\n+\tgit am --empty=keep cover-letter.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\t{\n+\t\techo \"*** SUBJECT HERE ***\" &&\n+\t\techo &&\n+\t\techo \"*** BLURB HERE ***\" &&\n+\t\techo &&\n+\t\techo \"A U Thor (1):\" &&\n+\t\tprintf \"  \" &&\n+\t\tgit show empty-commit --format=\"%B\" &&\n+\t\techo \"--\" &&\n+\t\tgit version | sed -e \"s/^git version //\" &&\n+\t\techo\n+\t} >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"441595","messageId":"e907a2b2faa1e3c5854504c23cc6e118c2125817.1637232636.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","subject":"[PATCH v6 3/3] am: throw an error when passing --empty option without value","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-18T10:50:36Z","receivedAt":"2021-11-18T10:51:13Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n builtin/am.c  | 13 +++++++------\n t/t4150-am.sh |  8 +++++++-\n 2 files changed, 14 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1a3ed87b445..5d487b5256b 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -185,12 +185,14 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n-static int am_option_parse_empty_commit(const struct option *opt,\n+static int am_option_parse_empty(const struct option *opt,\n \t\t\t\t     const char *arg, int unset)\n {\n \tint *opt_value = opt->value;\n \n-\tif (unset || !strcmp(arg, \"die\"))\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n \t\t*opt_value = DIE_EMPTY_COMMIT;\n \telse if (!strcmp(arg, \"drop\"))\n \t\t*opt_value = DROP_EMPTY_COMMIT;\n@@ -2391,10 +2393,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n-\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n-\t\t  \"(die|drop|keep)\",\n-\t\t  N_(\"specify how to handle empty patches\"),\n-\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 3119556884d..c32d21e80da 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1165,8 +1165,14 @@ test_expect_success 'still output error with --empty when meeting empty files' '\n \ttest_cmp expected actual\n '\n \n-test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n+test_expect_success 'invalid when passing no value for the --empty option' '\n \tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n \ttest_must_fail git am empty-commit.patch >err &&\n \ttest_path_is_dir .git/rebase-apply &&\n \ttest_i18ngrep \"Patch is empty.\" err &&\n-- \ngitgitgadget\n"},{"id":"441675","messageId":"xmqqilwpuiv4.fsf@gitster.g","threadId":"56877","inReplyTo":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-18T23:47:11Z","receivedAt":"2021-11-18T23:47:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\nAs per https://git-scm.com/docs/SubmittingPatches#sign-off:\n\n   Please don’t hide your real name.\n\nI suspect your real name is not Aleen, but something with x and w in\nit.\n\n\n"},{"id":"441679","messageId":"xmqqwnl5rmi3.fsf@gitster.g","threadId":"56877","inReplyTo":"96d8573dc808bc32990842675ca32a0d1e8a8cef.1637232636.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T00:56:52Z","receivedAt":"2021-11-19T00:56:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n\nHere is a place to explain to readers what it exactly means to\n\"support --empty option to handle empty patches\".  You know what you\nmeant, I happen to know what you meant because I haven't forgotten\nyet, but we are writing for developers in the future who need to fix\nbugs in 6 months down the road to decide if this commit is involved\nin the bug in \"git am\" they are hunting.\n\n> Signed-off-by: Aleen <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-am.txt |  9 +++++\n>  builtin/am.c             | 48 +++++++++++++++++++++++---\n>  t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 125 insertions(+), 5 deletions(-)\n>\n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index 0a4a984dfde..665bc89ca9f 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -16,6 +16,7 @@ SYNOPSIS\n>  \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n>  \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n>  \t [--quoted-cr=<action>]\n> +\t [--empty=(die|drop|keep)]\n>  \t [(<mbox> | <Maildir>)...]\n>  'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n>  \n> @@ -63,6 +64,14 @@ OPTIONS\n>  --quoted-cr=<action>::\n>  \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n>  \n> +--empty-commit=(die|drop|keep)::\n> +\tThe command usually errors out when seeing an input e-mail\n> +\tmessage that lacks a patch. When this option is set to\n> +\t'drop', skip such an e-mail message without outputting error.\n\n\"without outputting error\" -> \"instead\".\n\nThe first sentence used \"to error\" as a verb to mean \"stop the\noperation in the middle with a message that diagnoses what is\nwrong\".  Saying \"outputting error\" to use the same word as a noun is\nnot a good idea, especially the use of verb \"to output\", as it makes\nthe word \"error\" look as if it is only the \"message\" part, and it\nbecomes unclear if the \"stopping due to an error\" is also skipped.\n\nI actually think it makes sense to give a notice/info message when\n\"drop\" skips an empty patch, unless \"--quiet\" is given.\n\n> +\tWhen this option is set to 'keep', create an empty commit,\n> +\trecording the contents of the e-mail message as its log.\n> +\t'die' is specified by default.\n\n\"is specified by default\" sounds a bit strange.  Perhaps lose the\nlast sentence, and update the first line like so:\n\n    By default, or when the option is set to 'die', the command\n    errors out on an input e-mail message that lacks a patch.\n\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 8677ea2348a..1a3ed87b445 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -87,6 +87,12 @@ enum show_patch_type {\n>  \tSHOW_PATCH_DIFF = 1,\n>  };\n>  \n> +enum empty_action {\n> +\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n> +\tDROP_EMPTY_COMMIT,     /* skip without outputting errors */\n\nI think the user deserves \"info:\" (unless \"--quiet\").\n\n> +\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n> +};\n> +\n>  struct am_state {\n>  \t/* state directory path */\n>  \tchar *dir;\n> @@ -118,6 +124,7 @@ struct am_state {\n>  \tint message_id;\n>  \tint scissors; /* enum scissors_type */\n>  \tint quoted_cr; /* enum quoted_cr_action */\n> +\tint empty_type; /* enum empty_action */\n>  \tstruct strvec git_apply_opts;\n>  \tconst char *resolvemsg;\n>  \tint committer_date_is_author_date;\n> @@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n>  \treturn 0;\n>  }\n>  \n> +static int am_option_parse_empty_commit(const struct option *opt,\n> +\t\t\t\t     const char *arg, int unset)\n> +{\n> +\tint *opt_value = opt->value;\n> +\n> +\tif (unset || !strcmp(arg, \"die\"))\n> +\t\t*opt_value = DIE_EMPTY_COMMIT;\n> +\telse if (!strcmp(arg, \"drop\"))\n> +\t\t*opt_value = DROP_EMPTY_COMMIT;\n> +\telse if (!strcmp(arg, \"keep\"))\n> +\t\t*opt_value = KEEP_EMPTY_COMMIT;\n> +\telse\n> +\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n> +\n> +\treturn 0;\n> +}\n\nOK.\n\n> @@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)\n>  \t\tif (state->interactive && do_interactive(state))\n>  \t\t\tgoto next;\n>  \n> +\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> +\t\t\tif (state->empty_type == DROP_EMPTY_COMMIT)\n> +\t\t\t\tgoto next;\n> +\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n> +\t\t\t\tif (run_applypatch_msg_hook(state))\n> +\t\t\t\t\texit(1);\n> +\t\t\t\telse\n> +\t\t\t\t\tgoto commit;\n\nIt probably is easier to read without \"else\", i.e.\n\n\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n\t\t\t\tif (run_applypatch_msg_hook(state))\n\t\t\t\t\texit(1);\n\t\t\t\tgoto commit;\n\nRunning hook here and outside this if() block a bit ugly.  Can't we\ninstead set a flag variable or something and let the existing call\nto run_applypatch_msg_hook() used?\n\n> +\t\t\t} else if (state->empty_type == DIE_EMPTY_COMMIT) {\n> +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\t\tdie_user_resolve(state);\n> +\t\t\t}\n\nAlso, if developers ever add another possible value for the\nstate->empty_type member (by the way, \"empty\" in its name is good,\nbut \"type\" is iffy), it would be a bug if they do not consider what\nshould happen inside this \"if (is_empty_or_missing_file())\" block,\nno?  In such a case, instead of if/else if/... cascade, consider\nusing switch().  Taking them together.\n \n\twhile (state->cur <= state->last) {\n        \tconst char *mail = am_path(state, msgnum(state));\n\t\tint apply_status;\n\t\tint to_keep;\n\t\t...\n\t\tto_keep = 0;\n\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n\t\t\tswitch (state->empty_type) {\n\t\t\tcase DROP_EMPTY_COMMIT:\n                                say(state, stdout, _(\"Skipping empty patch '%.*s\",\n\t\t\t\t\tlinelen(state->msg0, state->msg);\n\t\t\t\tgoto next;\n\t\t\t\tbreak;\n\t\t\tcase KEEP_EMPTY_COMMIT:\n\t\t\t\tto_keep = 1;\n\t\t\t\tbreak;\n\t\t\tcase DIE_EMPTY_COMMIT:\n\t\t\t\tpritnf_ln(_(\"Patch is empty.\"));\n\t\t\t\tdie_user_resolve(state);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (run_applypatch_msg_hook(state))\n\t\t\texit(1);\n\t\tif (to_keep)\n\t\t\tgoto commit; /* empty: just record a message */\n\nor something like that.\n\n> diff --git a/t/t4150-am.sh b/t/t4150-am.sh\n> index 2aaaa0d7ded..3119556884d 100755\n> --- a/t/t4150-am.sh\n> +++ b/t/t4150-am.sh\n> @@ -196,6 +196,13 @@ test_expect_success setup '\n>  \n>  \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n>  \n> +\tgit checkout -b empty-commit &&\n> +\tgit commit -m \"empty commit\" --allow-empty &&\n> +\n> +\tgit format-patch --stdout empty-commit^ >empty.patch &&\n\nUnless this is a test for \"format-patch\" command to make sure that\nthe above command will not output a single byte, I would rather see\nthe above line written like this:\n\n\t: >empty.patch &&\n\n> +\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n\nThis only creates a cover letter without anything, which sort of\nlooks like an empty commit.\n\n> +\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n\nAnd this gives another empty patch with only a message.\n\nI would sort-of understand if there is\n\n\tgit format-patch --stdout --always --cover-letter HEAD^ >both.patch\n\nthat has two e-mail messages in it, but what's the significance of\ntesting both cover-letter.patch and empty-commit.patch?\n\nThe receiving end would not care what's in the actual e-mail\nmessage, as long as it lacks the patch part, no?  Or am I missing\nsome subtle reason why we want to test both?\n\n> @@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '\n>  \tgit -C client am ../patch\n>  '\n>  \n> +test_expect_success 'still output error with --empty when meeting empty files' '\n> +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n\nNobody will understand \"still\", unless they know it used to be\nunconditionally a failure to feed an e-mail message that lacks a\npatch.\n\ntest_expect_success 'An empty input file is error regardless of --empty option' '\n\nor something like that?\n\n> +\techo Patch format detection failed. >expected &&\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n> +\tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am empty-commit.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply &&\n\nIf any command before \"rm\" fails, it won't be cleaned up.\n\n    test_expect_success 'a message without a patch is an error (default)' '\n\ttest_when_finished \"git am --abort || :\" &&\n\tgit checkout --detach empty-commit^ &&\n\ttest_must_fail git am empty-commit.patch >err &&\n\tgrep \"Patch is empty\" err\n    '\n\nor something like that.  The title says it tests the default case,\nso do not test --empty=die case in the same test.  Use a separate\ntest to do the same for the case where an explicit \"--empty=die\"\nis given.\n\nAs I said, I do not quite see the point of testing with both\nempty-commit.patch and cover-letter.patch (unless the latter has an\nempty commit and a cover letter, totally in two messages).\n\n> +\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply &&\n> +\n> +\ttest_must_fail git am --empty=die cover-letter.patch >err &&\n> +\ttest_path_is_dir .git/rebase-apply &&\n> +\ttest_i18ngrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply\n> +'\n> +\n> +test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty=drop empty-commit.patch >err &&\n\nThe output 'err' is used by nobody; do not redirect it and just\nleave the standard output alone.\n\n> +\ttest_path_is_missing .git/rebase-apply &&\n\nDon't test this condition when testing success or failure from\n\"am\".  That is too much implementation detail.\n\n> +\tgit rev-parse empty-commit^ >expected &&\n> +\tgit rev-parse HEAD >actual &&\n> +\ttest_cmp expected actual &&\n\nOK, we make sure that HEAD does not move.\n\n> +\tgit am --empty=drop cover-letter.patch >err &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\ttest_cmp_rev empty-commit^ HEAD\n\nLose this one?  As I said, I may be missing some subtle reason why\nboth need to be tested---and if it is the case, test it in a\nseparate test, not 2 of them in a single test like this.\n\n> +'\n> +\n> +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty=keep empty-commit.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\t{\n> +\t\tgit show empty-commit --format=\"%B\" &&\n> +\t\techo \"--\" &&\n\nDid this test pass?  Without a trailing whitespace after double-dash,\nI do not think this shell snippet reproduces a patch e-mail correctly\n\nIn any case, don't do this.  Imagine a future developer may want to\nimprove the patch output by adding other things after the \"-- \"\nsignature line, in addition to the \"git version\".  Or perhaps the\n\"git version\" trailer gets removed by default for privacy concerns.\n\nShould such changes be prevented from being made, only because this\ntest expects exact output we have today?  No.\n\nVerify what we _care_, e.g. \"does the title of the resulting commit\nreproduce the one-liner message given to '-m' when the empty commit\nwas made?\" is a sensible question to ask.\n\n> +\t\tgit version | sed -e \"s/^git version //\" &&\n> +\t\techo\n\n> +\t} >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\ttest_cmp actual expected &&\n> +\n> +\tgit am --empty=keep cover-letter.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\t{\n> +\t\techo \"*** SUBJECT HERE ***\" &&\n> +\t\techo &&\n> +\t\techo \"*** BLURB HERE ***\" &&\n> +\t\techo &&\n> +\t\techo \"A U Thor (1):\" &&\n> +\t\tprintf \"  \" &&\n> +\t\tgit show empty-commit --format=\"%B\" &&\n> +\t\techo \"--\" &&\n> +\t\tgit version | sed -e \"s/^git version //\" &&\n> +\t\techo\n\nDon't do this.  Imagine a future developer may want to improve the\ncover letter template.  Should that change be prevented from being\nmade, because this test expects exact output we have today?  No.\n\n> +\t} >expected &&\n> +\tgit show HEAD --format=\"%B\" >actual &&\n> +\ttest_cmp actual expected\n> +'\n> +\n>  test_done\n\nThanks.\n"},{"id":"441680","messageId":"xmqqr1bdrlpq.fsf@gitster.g","threadId":"56877","inReplyTo":"e907a2b2faa1e3c5854504c23cc6e118c2125817.1637232636.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 3/3] am: throw an error when passing --empty option without value","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T01:13:53Z","receivedAt":"2021-11-19T01:13:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n\nHere is a place for you to explain why this change might be a good\nidea to defend it.  If we applied 1/3 and 2/3 but not this one, what\nhappens?  \"am --empty\" (not \"am --empty=<choice>\") is accepted and\nbehaves just like \"am --empty=die\"?  Is that a bad thing?\n\nIf it is unconditonally a bad thing, then this patch does not stand\non its own.  It should be squashed into the previous step; otherwise\nit would be \"oops, we didn't think it through and failed to detect\nan argument-less --error as an error in the previous step, and here\nis a fix for that mistake\".  We try not to deliberately commit a\nmistake and then fix it to gain commit counts in this project (or\nputting it differently, we try to pretend we are better than we\nreally are and did not make a mistake in the first place).\n\nOr are you giving list participants and reviewers a choice between\n\"'--empty' is a synonym to '--empty=die'\" and \"'--empty' alone is an\nerror\" because you cannot decide yourself?  If that is the case, you\nreally really need to make it clear in this place between \"From:\"\nand \"Signed-off-by:\".  Why would we want to use this patch?  Why\nwould we be better without this patch?\n\nI haven't thought things through, but my gut feeling is that the\nsemantics of the --empty=<choice> option is better _with_ this,\nin other words, it makes more sense to forbid \"--empty\" alone.  So\nif I were doing this series, I would probably have squashed this\ninto the previous one, making it a two-patch series.\n\nThanks.\n\n\n> Signed-off-by: Aleen <aleen42@vip.qq.com>\n> ---\n>  builtin/am.c  | 13 +++++++------\n>  t/t4150-am.sh |  8 +++++++-\n>  2 files changed, 14 insertions(+), 7 deletions(-)\n\n\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 1a3ed87b445..5d487b5256b 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -185,12 +185,14 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n>  \treturn 0;\n>  }\n>  \n> -static int am_option_parse_empty_commit(const struct option *opt,\n> +static int am_option_parse_empty(const struct option *opt,\n>  \t\t\t\t     const char *arg, int unset)\n>  {\n>  \tint *opt_value = opt->value;\n>  \n> -\tif (unset || !strcmp(arg, \"die\"))\n> +\tBUG_ON_OPT_NEG(unset);\n\n\n\n> +\tif (!strcmp(arg, \"die\"))\n>  \t\t*opt_value = DIE_EMPTY_COMMIT;\n>  \telse if (!strcmp(arg, \"drop\"))\n>  \t\t*opt_value = DROP_EMPTY_COMMIT;\n> @@ -2391,10 +2393,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>  \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n>  \t\t  N_(\"GPG-sign commits\"),\n>  \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n> -\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n> -\t\t  \"(die|drop|keep)\",\n> -\t\t  N_(\"specify how to handle empty patches\"),\n> -\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n> +\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n> +\t\t  N_(\"how to handle empty patches\"),\n> +\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n>  \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n>  \t\t\tN_(\"(internal use for git-rebase)\")),\n>  \t\tOPT_END()\n> diff --git a/t/t4150-am.sh b/t/t4150-am.sh\n> index 3119556884d..c32d21e80da 100755\n> --- a/t/t4150-am.sh\n> +++ b/t/t4150-am.sh\n> @@ -1165,8 +1165,14 @@ test_expect_success 'still output error with --empty when meeting empty files' '\n>  \ttest_cmp expected actual\n>  '\n>  \n> -test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n> +test_expect_success 'invalid when passing no value for the --empty option' '\n>  \tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n> +\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n> +\ttest_cmp expected err\n> +'\n> +\n> +test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n>  \ttest_must_fail git am empty-commit.patch >err &&\n>  \ttest_path_is_dir .git/rebase-apply &&\n>  \ttest_i18ngrep \"Patch is empty.\" err &&\n"},{"id":"441682","messageId":"b9c1244.36.17d35decb26.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqilwpuiv4.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-19T01:45:33Z","receivedAt":"2021-11-19T01:45:44Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> -----Original Messages-----From:\"Junio C Hamano\" <gitster@pobox.com>Sent Time:2021-11-19 07:47:11 (Friday)To:\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com>Cc:git@vger.kernel.org, \"René Scharfe\" <l.s.r@web.de>, \"Phillip Wood\" <phillip.wood123@gmail.com>, \"Aleen 徐沛文\" <pwxu@coremail.cn>, Aleen <aleen42@vip.qq.com>Subject:Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches\n> \n> \n> As per https://git-scm.com/docs/SubmittingPatches#sign-off:\n> \n>    Please don’t hide your real name.\n> \n> I suspect your real name is not Aleen, but something with x and w in\n> it.\n>\n\nI have used \"Aleen\" <aleen42@vip.qq.com> as my GitHub account to send this pull request, but the e-mail service disgusts me that it is slow to send emails to the domain \"vger.kernel.org\". So I decided to use another mail service to discuss, \"Aleen 徐沛文\" <pwxu@coremail.cn> as you saw.\n\nIn the open-source world, I usually signed off \"Aleen\" rather than \"Aleen 徐沛文\", and is that necessary to change?"},{"id":"441683","messageId":"526ec6c0.87.17d35f63231.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqr1bdrlpq.fsf@gitster.g","subject":"Re: [PATCH v6 3/3] am: throw an error when passing --empty option without value","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-19T02:11:06Z","receivedAt":"2021-11-19T02:11:12Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> Here is a place for you to explain why this change might be a good\n> idea to defend it.  If we applied 1/3 and 2/3 but not this one, what\n> happens?  \"am --empty\" (not \"am --empty=<choice>\") is accepted and\n> behaves just like \"am --empty=die\"?  Is that a bad thing?\n> \n> If it is unconditonally a bad thing, then this patch does not stand\n> on its own.  It should be squashed into the previous step; otherwise\n> it would be \"oops, we didn't think it through and failed to detect\n> an argument-less --error as an error in the previous step, and here\n> is a fix for that mistake\".  We try not to deliberately commit a\n> mistake and then fix it to gain commit counts in this project (or\n> putting it differently, we try to pretend we are better than we\n> really are and did not make a mistake in the first place).\n> \n> Or are you giving list participants and reviewers a choice between\n> \"'--empty' is a synonym to '--empty=die'\" and \"'--empty' alone is an\n> error\" because you cannot decide yourself?  If that is the case, you\n> really really need to make it clear in this place between \"From:\"\n> and \"Signed-off-by:\".  Why would we want to use this patch?  Why\n> would we be better without this patch?\n> \n> I haven't thought things through, but my gut feeling is that the\n> semantics of the --empty=<choice> option is better _with_ this,\n> in other words, it makes more sense to forbid \"--empty\" alone.  So\n> if I were doing this series, I would probably have squashed this\n> into the previous one, making it a two-patch series.\n> \n> Thanks.\n\nHamano, I could not make this decision like what you said between \"'--empty' is a synonym to '--empty=die'\" and \"'--empty' alone is an error\". In my opinion, it is a little wired that '--empty' should not act as \"died\" semantically. Reversely, it may be better to support a synonym between '--quiet' and '--empty=skip'.\n\nI will squash the commit into the previous one later and hope all participants and reviewers make a decision.\n"},{"id":"441687","messageId":"75b05498.16b.17d36694413.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqilwpuiv4.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-19T04:16:48Z","receivedAt":"2021-11-19T04:17:19Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> As per https://git-scm.com/docs/SubmittingPatches#sign-off:\n> \n>    Please don’t hide your real name.\n> \n> I suspect your real name is not Aleen, but something with x and w in\n> it.\n\nBTW, gitgitgadget has defined that developers should sign off the user name\nof the GitHub account, and I can't change it at all."},{"id":"441688","messageId":"pull.1076.v7.git.1637298298.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v6.git.1637232636.gitgitgadget@gmail.com","subject":"[PATCH v7 0/2] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-19T05:04:56Z","receivedAt":"2021-11-19T05:05:08Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\ncc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:\nAleen 徐沛文 pwxu@coremail.cn\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n\n Documentation/git-am.txt           |  8 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 55 +++++++++++++++++++++++++++---\n po/bg.po                           |  2 +-\n po/ca.po                           |  2 +-\n po/de.po                           |  2 +-\n po/el.po                           |  2 +-\n po/es.po                           |  2 +-\n po/fr.po                           |  2 +-\n po/git.pot                         |  2 +-\n po/id.po                           |  2 +-\n po/it.po                           |  2 +-\n po/ko.po                           |  2 +-\n po/pl.po                           |  2 +-\n po/pt_PT.po                        |  8 ++---\n po/ru.po                           |  4 +--\n po/sv.po                           |  2 +-\n po/tr.po                           |  2 +-\n po/vi.po                           |  2 +-\n po/zh_CN.po                        |  9 +++--\n po/zh_TW.po                        |  7 +++-\n t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++\n 22 files changed, 145 insertions(+), 29 deletions(-)\n\n\nbase-commit: ca35af825273b98fc8dc11527488952f5db8eb80\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v6:\n\n 1:  9f1b3dd6d0b ! 1:  d612bc49d57 doc: git-format-patch: describe the option --always\n     @@ Metadata\n       ## Commit message ##\n          doc: git-format-patch: describe the option --always\n      \n     +    This commit has described how to use '--always' option in the command\n     +    'git-format-patch' to include patches for commits that emit no changes.\n     +\n          Signed-off-by: Aleen <aleen42@vip.qq.com>\n      \n       ## Documentation/git-format-patch.txt ##\n 2:  96d8573dc80 ! 2:  9e60e77c041 am: support --empty option to handle empty patches\n     @@ Metadata\n      Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n     -    am: support --empty option to handle empty patches\n     +    am: support --empty=<option> to handle empty patches\n     +\n     +    Since that the command 'git-format-patch' can include patches of\n     +    commits that emit no changes, the 'git-am' command should also\n     +    support an option, named as '--empty', to specify how to handle\n     +    those empty patches. In this commit, we have implemented three\n     +    valid options ('die', 'drop' and 'keep').\n      \n          Signed-off-by: Aleen <aleen42@vip.qq.com>\n      \n     @@ Documentation/git-am.txt: OPTIONS\n       \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n      +--empty-commit=(die|drop|keep)::\n     -+\tThe command usually errors out when seeing an input e-mail\n     -+\tmessage that lacks a patch. When this option is set to\n     -+\t'drop', skip such an e-mail message without outputting error.\n     ++\tBy default, or when the option is set to 'die', the command\n     ++\terrors out on an input e-mail message that lacks a patch. When\n     ++\tthis option is set to 'drop', skip such an e-mail message instead.\n      +\tWhen this option is set to 'keep', create an empty commit,\n      +\trecording the contents of the e-mail message as its log.\n     -+\t'die' is specified by default.\n      +\n       -m::\n       --message-id::\n     @@ builtin/am.c: enum show_patch_type {\n       \n      +enum empty_action {\n      +\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n     -+\tDROP_EMPTY_COMMIT,     /* skip without outputting errors */\n     ++\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n      +\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n      +};\n      +\n     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,\n       \treturn 0;\n       }\n       \n     -+static int am_option_parse_empty_commit(const struct option *opt,\n     ++static int am_option_parse_empty(const struct option *opt,\n      +\t\t\t\t     const char *arg, int unset)\n      +{\n      +\tint *opt_value = opt->value;\n      +\n     -+\tif (unset || !strcmp(arg, \"die\"))\n     ++\tBUG_ON_OPT_NEG(unset);\n     ++\n     ++\tif (!strcmp(arg, \"die\"))\n      +\t\t*opt_value = DIE_EMPTY_COMMIT;\n      +\telse if (!strcmp(arg, \"drop\"))\n      +\t\t*opt_value = DROP_EMPTY_COMMIT;\n     @@ builtin/am.c: static int parse_mail(struct am_state *state, const char *mail)\n       \tstrbuf_addstr(&msg, \"\\n\\n\");\n       \tstrbuf_addbuf(&msg, &mi.log_message);\n       \tstrbuf_stripspace(&msg, 0);\n     +@@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n     + \twhile (state->cur <= state->last) {\n     + \t\tconst char *mail = am_path(state, msgnum(state));\n     + \t\tint apply_status;\n     ++\t\tint to_keep;\n     + \n     + \t\treset_ident_date();\n     + \n      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n       \t\tif (state->interactive && do_interactive(state))\n       \t\t\tgoto next;\n       \n     ++\t\tto_keep = 0;\n      +\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n     -+\t\t\tif (state->empty_type == DROP_EMPTY_COMMIT)\n     ++\t\t\tswitch (state->empty_type) {\n     ++\t\t\tcase DROP_EMPTY_COMMIT:\n     ++\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n      +\t\t\t\tgoto next;\n     -+\t\t\telse if (state->empty_type == KEEP_EMPTY_COMMIT) {\n     -+\t\t\t\tif (run_applypatch_msg_hook(state))\n     -+\t\t\t\t\texit(1);\n     -+\t\t\t\telse\n     -+\t\t\t\t\tgoto commit;\n     -+\t\t\t} else if (state->empty_type == DIE_EMPTY_COMMIT) {\n     ++\t\t\t\tbreak;\n     ++\t\t\tcase KEEP_EMPTY_COMMIT:\n     ++\t\t\t\tto_keep = 1;\n     ++\t\t\t\tbreak;\n     ++\t\t\tcase DIE_EMPTY_COMMIT:\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tdie_user_resolve(state);\n     ++\t\t\t\tbreak;\n      +\t\t\t}\n      +\t\t}\n      +\n       \t\tif (run_applypatch_msg_hook(state))\n       \t\t\texit(1);\n     ++\t\tif (to_keep)\n     ++\t\t\tgoto commit;\n     + \n     + \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n       \n      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n       \t\t\tdie_user_resolve(state);\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n       \t\t  N_(\"GPG-sign commits\"),\n       \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n     -+\t\t{ OPTION_CALLBACK, 0, \"empty\", &state.empty_type,\n     -+\t\t  \"(die|drop|keep)\",\n     -+\t\t  N_(\"specify how to handle empty patches\"),\n     -+\t\t  PARSE_OPT_OPTARG, am_option_parse_empty_commit },\n     ++\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n     ++\t\t  N_(\"how to handle empty patches\"),\n     ++\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n       \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n       \t\t\tN_(\"(internal use for git-rebase)\")),\n       \t\tOPT_END()\n      \n     + ## po/bg.po ##\n     +@@ po/bg.po: msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"\"\n     + \"Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Прилагане: %.*s\"\n     +\n     + ## po/ca.po ##\n     +@@ po/ca.po: msgstr \"no s'ha pogut escriure el fitxer d'índex\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Índex brut: no es poden aplicar pedaços (bruts: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"S'està aplicant: %.*s\"\n     +\n     + ## po/de.po ##\n     +@@ po/de.po: msgstr \"Konnte Index-Datei nicht schreiben.\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Geänderter Index: kann Patches nicht anwenden (geändert: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Wende an: %.*s\"\n     +\n     + ## po/el.po ##\n     +@@ po/el.po: msgstr \"Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"\"\n     + \n     +-#: builtin/am.c:1749 builtin/am.c:1817\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"\"\n     +\n     + ## po/es.po ##\n     +@@ po/es.po: msgstr \"no es posible escribir el archivo índice\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Índice sucio: no se puede aplicar parches (sucio: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Aplicando: %.*s\"\n     +\n     + ## po/fr.po ##\n     +@@ po/fr.po: msgstr \"impossible d'écrire le fichier d'index\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Index sale : impossible d'appliquer des patchs (sales : %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Application de  %.*s\"\n     +\n     + ## po/git.pot ##\n     +@@ po/git.pot: msgstr \"\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"\"\n     +\n     + ## po/id.po ##\n     +@@ po/id.po: msgstr \"tidak dapat menulis berkas indeks\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Menerapkan: %.*s\"\n     +\n     + ## po/it.po ##\n     +@@ po/it.po: msgstr \"impossibile scrivere il file indice\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Indice sporco: impossibile applicare le patch (elemento sporco: %s)\"\n     + \n     +-#: builtin/am.c:1761 builtin/am.c:1829\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Applicazione in corso: %.*s\"\n     +\n     + ## po/ko.po ##\n     +@@ po/ko.po: msgstr \"적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)\"\n     + \n     +-#: builtin/am.c:1808 builtin/am.c:1879\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"적용하는 중: %.*s\"\n     +\n     + ## po/pl.po ##\n     +@@ po/pl.po: msgstr \"nie można zapisać pliku indeksu\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Brudny indeks: nie można zastosować łatek (brudny: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Stosowanie: %.*s\"\n     +\n     + ## po/pt_PT.po ##\n     +@@\n     + #   bisect                           |  bisetar\n     + #   blame                            |  blame\n     + #   blob object                      |  objeto-blob\n     +-#   branch                           |  ramo \n     ++#   branch                           |  ramo\n     + #   bug                              |  bug\n     + #   bundle                           |  conjunto\n     + #   bypass                           |  desviar\n     +@@\n     + #   loose refs                       |  refs soltas\n     + #   mark                             |  marca\n     + #   master                           |  master\n     +-#   merge                            |  junção \n     ++#   merge                            |  junção\n     + #   mergetag                         |  etiqueta-junção\n     + #   object                           |  objeto\n     + #   object database                  |  base dados de objeto\n     +@@\n     + #   token                            |  token\n     + #   unset                            |  desdefinir\n     + #   untrack                          |  desmonitorizar\n     +-#   \n     ++#\n     + #\n     + msgid \"\"\n     + msgstr \"\"\n     +@@ po/pt_PT.po: msgstr \"incapaz escrever ficheiro de index\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Index sujo: incapaz aplicar patches (sujo: %s)\"\n     + \n     +-#: builtin/am.c:1797 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"A aplicar: %.*s\"\n     +\n     + ## po/ru.po ##\n     +@@\n     + # SOME DESCRIPTIVE TITLE.\n     + # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n     + # This file is distributed under the same license as the PACKAGE package.\n     +-# \n     ++#\n     + # Translators:\n     + # Alexander Golubev <fatzer2@gmail.com>, 2020\n     + # Dimitriy Ryazantcev <DJm00n@mail.ru>, 2014-2021\n     +@@ po/ru.po: msgstr \"не удалось записать индекс\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Индекс изменён: нельзя применять патчи (изменено: %s)\"\n     + \n     +-#: builtin/am.c:1748 builtin/am.c:1816\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Применение: %.*s\"\n     +\n     + ## po/sv.po ##\n     +@@ po/sv.po: msgstr \"kan inte skriva indexfil\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Tillämpar: %.*s\"\n     +\n     + ## po/tr.po ##\n     +@@ po/tr.po: msgstr \"indeks dosyası yazılamıyor\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Kirli indeks: Yamalar uygulanamıyor (kirli: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Uygulanıyor: %.*s\"\n     +\n     + ## po/vi.po ##\n     +@@ po/vi.po: msgstr \"không thể ghi tập tin lưu mục lục\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"Áp dụng: %.*s\"\n     +\n     + ## po/zh_CN.po ##\n     +@@ po/zh_CN.po: msgstr \"坏的索引文件 sha1 签名\"\n     + msgid \"index uses %.4s extension, which we do not understand\"\n     + msgstr \"索引使用不被支持的 %.4s 扩展\"\n     + \n     +-# \t\n     ++#\n     + #: read-cache.c:1834\n     + #, c-format\n     + msgid \"ignoring %.4s extension\"\n     +@@ po/zh_CN.po: msgstr \"无法写入索引文件\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"脏索引：不能应用补丁（脏文件：%s）\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"应用：%.*s\"\n     + \n     ++#: builtin/am.c:1818\n     ++#, c-format\n     ++msgid \"Skipping: %.*s\"\n     ++msgstr \"跳过：%.*s\"\n     ++\n     + #: builtin/am.c:1815\n     + msgid \"No changes -- Patch already applied.\"\n     + msgstr \"没有变更 —— 补丁已经应用过。\"\n     +\n     + ## po/zh_TW.po ##\n     +@@ po/zh_TW.po: msgstr \"無法寫入索引檔案\"\n     + msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     + msgstr \"髒索引：不能套用修補檔（髒檔案：%s）\"\n     + \n     +-#: builtin/am.c:1798 builtin/am.c:1865\n     ++#: builtin/am.c:1834 builtin/am.c:1902\n     + #, c-format\n     + msgid \"Applying: %.*s\"\n     + msgstr \"套用：%.*s\"\n     + \n     ++#: builtin/am.c:1818\n     ++#, c-format\n     ++msgid \"Skipping: %.*s\"\n     ++msgstr \"忽略：%.*s\"\n     ++\n     + #: builtin/am.c:1815\n     + msgid \"No changes -- Patch already applied.\"\n     + msgstr \"沒有變更——修補檔已經套用過。\"\n     +\n       ## t/t4150-am.sh ##\n      @@ t/t4150-am.sh: test_expect_success setup '\n       \n     @@ t/t4150-am.sh: test_expect_success setup '\n      +\tgit checkout -b empty-commit &&\n      +\tgit commit -m \"empty commit\" --allow-empty &&\n      +\n     -+\tgit format-patch --stdout empty-commit^ >empty.patch &&\n     -+\tgit format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&\n     ++\t: >empty.patch &&\n      +\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n      +\n       \t# reset time\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n       \tgit -C client am ../patch\n       '\n       \n     -+test_expect_success 'still output error with --empty when meeting empty files' '\n     ++test_expect_success 'An empty input file is error regardless of --empty option' '\n      +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n      +\techo Patch format detection failed. >expected &&\n      +\ttest_cmp expected actual\n      +'\n      +\n     -+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '\n     ++test_expect_success 'invalid when passing the --empty option alone' '\n      +\tgit checkout empty-commit^ &&\n     ++\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n     ++\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n     ++\ttest_cmp expected err\n     ++'\n     ++\n     ++test_expect_success 'a message without a patch is an error (default)' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n     -+\ttest_path_is_dir .git/rebase-apply &&\n     -+\ttest_i18ngrep \"Patch is empty.\" err &&\n     -+\trm -fr .git/rebase-apply &&\n     ++\tgrep \"Patch is empty\" err &&\n     ++\trm -fr .git/rebase-apply\n     ++'\n      +\n     ++test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n     -+\ttest_path_is_dir .git/rebase-apply &&\n     -+\ttest_i18ngrep \"Patch is empty.\" err &&\n     -+\trm -fr .git/rebase-apply &&\n     -+\n     -+\ttest_must_fail git am --empty=die cover-letter.patch >err &&\n     -+\ttest_path_is_dir .git/rebase-apply &&\n     -+\ttest_i18ngrep \"Patch is empty.\" err &&\n     ++\tgrep \"Patch is empty.\" err &&\n      +\trm -fr .git/rebase-apply\n      +'\n      +\n     -+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '\n     -+\tgit am --empty=drop empty-commit.patch >err &&\n     -+\ttest_path_is_missing .git/rebase-apply &&\n     ++test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n     ++\tgit am --empty=drop empty-commit.patch >output &&\n      +\tgit rev-parse empty-commit^ >expected &&\n      +\tgit rev-parse HEAD >actual &&\n      +\ttest_cmp expected actual &&\n     -+\n     -+\tgit am --empty=drop cover-letter.patch >err &&\n     -+\ttest_path_is_missing .git/rebase-apply &&\n     -+\ttest_cmp_rev empty-commit^ HEAD\n     ++\tgrep \"Skipping: empty commit\" output\n      +'\n      +\n      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n      +\tgit am --empty=keep empty-commit.patch &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\t{\n     -+\t\tgit show empty-commit --format=\"%B\" &&\n     -+\t\techo \"--\" &&\n     -+\t\tgit version | sed -e \"s/^git version //\" &&\n     -+\t\techo\n     -+\t} >expected &&\n     -+\tgit show HEAD --format=\"%B\" >actual &&\n     -+\ttest_cmp actual expected &&\n     -+\n     -+\tgit am --empty=keep cover-letter.patch &&\n     -+\ttest_path_is_missing .git/rebase-apply &&\n     -+\t{\n     -+\t\techo \"*** SUBJECT HERE ***\" &&\n     -+\t\techo &&\n     -+\t\techo \"*** BLURB HERE ***\" &&\n     -+\t\techo &&\n     -+\t\techo \"A U Thor (1):\" &&\n     -+\t\tprintf \"  \" &&\n     -+\t\tgit show empty-commit --format=\"%B\" &&\n     -+\t\techo \"--\" &&\n     -+\t\tgit version | sed -e \"s/^git version //\" &&\n     -+\t\techo\n     -+\t} >expected &&\n     -+\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgit show empty-commit --format=\"%s\" >expected &&\n     ++\tgit show HEAD --format=\"%s\" >actual &&\n      +\ttest_cmp actual expected\n      +'\n      +\n 3:  e907a2b2faa < -:  ----------- am: throw an error when passing --empty option without value\n\n-- \ngitgitgadget\n"},{"id":"441689","messageId":"d612bc49d57291f5ae67eb7cb4dabc3a1f2af7e7.1637298299.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v7.git.1637298298.gitgitgadget@gmail.com","subject":"[PATCH v7 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-19T05:04:57Z","receivedAt":"2021-11-19T05:05:11Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441690","messageId":"9e60e77c041bca28a50d706c865eb776e5fe2ec2.1637298299.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v7.git.1637298298.gitgitgadget@gmail.com","subject":"[PATCH v7 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-19T05:04:58Z","receivedAt":"2021-11-19T05:05:11Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: Aleen <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n po/bg.po                 |  2 +-\n po/ca.po                 |  2 +-\n po/de.po                 |  2 +-\n po/el.po                 |  2 +-\n po/es.po                 |  2 +-\n po/fr.po                 |  2 +-\n po/git.pot               |  2 +-\n po/id.po                 |  2 +-\n po/it.po                 |  2 +-\n po/ko.po                 |  2 +-\n po/pl.po                 |  2 +-\n po/pt_PT.po              |  8 +++---\n po/ru.po                 |  4 +--\n po/sv.po                 |  2 +-\n po/tr.po                 |  2 +-\n po/vi.po                 |  2 +-\n po/zh_CN.po              |  9 +++++--\n po/zh_TW.po              |  7 ++++-\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 21 files changed, 140 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..1e6a64bf2ed 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty-commit=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/po/bg.po b/po/bg.po\nindex 51d7c7242f2..17d05bc9d4e 100644\n--- a/po/bg.po\n+++ b/po/bg.po\n@@ -11194,7 +11194,7 @@ msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"\"\n \"Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Прилагане: %.*s\"\ndiff --git a/po/ca.po b/po/ca.po\nindex 556b028cd8b..4035055c28e 100644\n--- a/po/ca.po\n+++ b/po/ca.po\n@@ -10931,7 +10931,7 @@ msgstr \"no s'ha pogut escriure el fitxer d'índex\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Índex brut: no es poden aplicar pedaços (bruts: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"S'està aplicant: %.*s\"\ndiff --git a/po/de.po b/po/de.po\nindex f00c21d896f..fbae81fc59f 100644\n--- a/po/de.po\n+++ b/po/de.po\n@@ -10999,7 +10999,7 @@ msgstr \"Konnte Index-Datei nicht schreiben.\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Geänderter Index: kann Patches nicht anwenden (geändert: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Wende an: %.*s\"\ndiff --git a/po/el.po b/po/el.po\nindex 703f46d0c7c..5a3d4d8a61e 100644\n--- a/po/el.po\n+++ b/po/el.po\n@@ -7801,7 +7801,7 @@ msgstr \"Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"\"\n \n-#: builtin/am.c:1749 builtin/am.c:1817\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"\"\ndiff --git a/po/es.po b/po/es.po\nindex ced2eb6d3d5..774a333cc64 100644\n--- a/po/es.po\n+++ b/po/es.po\n@@ -10840,7 +10840,7 @@ msgstr \"no es posible escribir el archivo índice\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Índice sucio: no se puede aplicar parches (sucio: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Aplicando: %.*s\"\ndiff --git a/po/fr.po b/po/fr.po\nindex e950f87add0..30a8a1b3fd1 100644\n--- a/po/fr.po\n+++ b/po/fr.po\n@@ -11013,7 +11013,7 @@ msgstr \"impossible d'écrire le fichier d'index\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Index sale : impossible d'appliquer des patchs (sales : %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Application de  %.*s\"\ndiff --git a/po/git.pot b/po/git.pot\nindex 78fea37065b..1367e12f3d0 100644\n--- a/po/git.pot\n+++ b/po/git.pot\n@@ -9985,7 +9985,7 @@ msgstr \"\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"\"\ndiff --git a/po/id.po b/po/id.po\nindex c1afc2a32f3..0821da2efee 100644\n--- a/po/id.po\n+++ b/po/id.po\n@@ -10306,7 +10306,7 @@ msgstr \"tidak dapat menulis berkas indeks\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Menerapkan: %.*s\"\ndiff --git a/po/it.po b/po/it.po\nindex c31560834d8..475a60dc1d1 100644\n--- a/po/it.po\n+++ b/po/it.po\n@@ -10229,7 +10229,7 @@ msgstr \"impossibile scrivere il file indice\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Indice sporco: impossibile applicare le patch (elemento sporco: %s)\"\n \n-#: builtin/am.c:1761 builtin/am.c:1829\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Applicazione in corso: %.*s\"\ndiff --git a/po/ko.po b/po/ko.po\nindex 5d190e52380..5a9d7eb0305 100644\n--- a/po/ko.po\n+++ b/po/ko.po\n@@ -5681,7 +5681,7 @@ msgstr \"적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)\"\n \n-#: builtin/am.c:1808 builtin/am.c:1879\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"적용하는 중: %.*s\"\ndiff --git a/po/pl.po b/po/pl.po\nindex 0ec127e14cc..f9768843fb0 100644\n--- a/po/pl.po\n+++ b/po/pl.po\n@@ -10811,7 +10811,7 @@ msgstr \"nie można zapisać pliku indeksu\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Brudny indeks: nie można zastosować łatek (brudny: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Stosowanie: %.*s\"\ndiff --git a/po/pt_PT.po b/po/pt_PT.po\nindex 70e5c6d9c16..e89894ca5dc 100644\n--- a/po/pt_PT.po\n+++ b/po/pt_PT.po\n@@ -24,7 +24,7 @@\n #   bisect                           |  bisetar\n #   blame                            |  blame\n #   blob object                      |  objeto-blob\n-#   branch                           |  ramo \n+#   branch                           |  ramo\n #   bug                              |  bug\n #   bundle                           |  conjunto\n #   bypass                           |  desviar\n@@ -74,7 +74,7 @@\n #   loose refs                       |  refs soltas\n #   mark                             |  marca\n #   master                           |  master\n-#   merge                            |  junção \n+#   merge                            |  junção\n #   mergetag                         |  etiqueta-junção\n #   object                           |  objeto\n #   object database                  |  base dados de objeto\n@@ -211,7 +211,7 @@\n #   token                            |  token\n #   unset                            |  desdefinir\n #   untrack                          |  desmonitorizar\n-#   \n+#\n #\n msgid \"\"\n msgstr \"\"\n@@ -10485,7 +10485,7 @@ msgstr \"incapaz escrever ficheiro de index\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Index sujo: incapaz aplicar patches (sujo: %s)\"\n \n-#: builtin/am.c:1797 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"A aplicar: %.*s\"\ndiff --git a/po/ru.po b/po/ru.po\nindex 993d106f1f9..69acd080d12 100644\n--- a/po/ru.po\n+++ b/po/ru.po\n@@ -1,7 +1,7 @@\n # SOME DESCRIPTIVE TITLE.\n # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n # This file is distributed under the same license as the PACKAGE package.\n-# \n+#\n # Translators:\n # Alexander Golubev <fatzer2@gmail.com>, 2020\n # Dimitriy Ryazantcev <DJm00n@mail.ru>, 2014-2021\n@@ -9398,7 +9398,7 @@ msgstr \"не удалось записать индекс\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Индекс изменён: нельзя применять патчи (изменено: %s)\"\n \n-#: builtin/am.c:1748 builtin/am.c:1816\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Применение: %.*s\"\ndiff --git a/po/sv.po b/po/sv.po\nindex e3b17b6a0ef..41f6be1c540 100644\n--- a/po/sv.po\n+++ b/po/sv.po\n@@ -10746,7 +10746,7 @@ msgstr \"kan inte skriva indexfil\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Tillämpar: %.*s\"\ndiff --git a/po/tr.po b/po/tr.po\nindex 06870900c4e..6c4d7ee8337 100644\n--- a/po/tr.po\n+++ b/po/tr.po\n@@ -10810,7 +10810,7 @@ msgstr \"indeks dosyası yazılamıyor\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Kirli indeks: Yamalar uygulanamıyor (kirli: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Uygulanıyor: %.*s\"\ndiff --git a/po/vi.po b/po/vi.po\nindex be197ab594f..5ed8c0239fe 100644\n--- a/po/vi.po\n+++ b/po/vi.po\n@@ -10807,7 +10807,7 @@ msgstr \"không thể ghi tập tin lưu mục lục\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"Áp dụng: %.*s\"\ndiff --git a/po/zh_CN.po b/po/zh_CN.po\nindex 60ffa45f7a0..13c4ae14a29 100644\n--- a/po/zh_CN.po\n+++ b/po/zh_CN.po\n@@ -6463,7 +6463,7 @@ msgstr \"坏的索引文件 sha1 签名\"\n msgid \"index uses %.4s extension, which we do not understand\"\n msgstr \"索引使用不被支持的 %.4s 扩展\"\n \n-# \t\n+#\n #: read-cache.c:1834\n #, c-format\n msgid \"ignoring %.4s extension\"\n@@ -10673,11 +10673,16 @@ msgstr \"无法写入索引文件\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"脏索引：不能应用补丁（脏文件：%s）\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"应用：%.*s\"\n \n+#: builtin/am.c:1818\n+#, c-format\n+msgid \"Skipping: %.*s\"\n+msgstr \"跳过：%.*s\"\n+\n #: builtin/am.c:1815\n msgid \"No changes -- Patch already applied.\"\n msgstr \"没有变更 —— 补丁已经应用过。\"\ndiff --git a/po/zh_TW.po b/po/zh_TW.po\nindex f1df8b60a91..d6259cc1cb6 100644\n--- a/po/zh_TW.po\n+++ b/po/zh_TW.po\n@@ -10538,11 +10538,16 @@ msgstr \"無法寫入索引檔案\"\n msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n msgstr \"髒索引：不能套用修補檔（髒檔案：%s）\"\n \n-#: builtin/am.c:1798 builtin/am.c:1865\n+#: builtin/am.c:1834 builtin/am.c:1902\n #, c-format\n msgid \"Applying: %.*s\"\n msgstr \"套用：%.*s\"\n \n+#: builtin/am.c:1818\n+#, c-format\n+msgid \"Skipping: %.*s\"\n+msgstr \"忽略：%.*s\"\n+\n #: builtin/am.c:1815\n msgid \"No changes -- Patch already applied.\"\n msgstr \"沒有變更——修補檔已經套用過。\"\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..16e966b2b73 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'An empty input file is error regardless of --empty option' '\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo Patch format detection failed. >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\trm -fr .git/rebase-apply\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441693","messageId":"xmqqmtm0snol.fsf@gitster.g","threadId":"56877","inReplyTo":"b9c1244.36.17d35decb26.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T05:46:02Z","receivedAt":"2021-11-19T05:46:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aleen 徐沛文 <pwxu@coremail.cn> writes:\n\n>> As per https://git-scm.com/docs/SubmittingPatches#sign-off:\n>> \n>>    Please don’t hide your real name.\n>> \n>> I suspect your real name is not Aleen, but something with x and w in\n>> it.\n>>\n>\n> I have used \"Aleen\" <aleen42@vip.qq.com> as my GitHub account to\n> send this pull request, but the e-mail service disgusts me that it\n> is slow to send emails to the domain \"vger.kernel.org\". So I\n> decided to use another mail service to discuss, \"Aleen 徐沛文\"\n> <pwxu@coremail.cn> as you saw.\n>\n> In the open-source world, I usually signed off \"Aleen\" rather than\n> \"Aleen 徐沛文\", and is that necessary to change?\n\nYes.\n\nAs the URL you were referred to explains, the sign-off procedure is\nto keep track of provenance of the code, which is a more \"legal\"\nformal requirement than just \"I use this pseudonym everywhere\".\nWhen a big company comes to us, claiming that \"this code is our\nintellectual property stolen from us\" and pointing at code added by\na patch from you, we do not want to see us in the position to have\nto say \"eh, somebody who uses psuedonym X signed DCO, but we do not\neven know their real name\".\n"},{"id":"441701","messageId":"2ebb863f.246.17d37140518.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqmtm0snol.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-19T07:23:18Z","receivedAt":"2021-11-19T07:23:30Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> Yes.\n> \n> As the URL you were referred to explains, the sign-off procedure is\n> to keep track of provenance of the code, which is a more \"legal\"\n> formal requirement than just \"I use this pseudonym everywhere\".\n> When a big company comes to us, claiming that \"this code is our\n> intellectual property stolen from us\" and pointing at code added by\n> a patch from you, we do not want to see us in the position to have\n> to say \"eh, somebody who uses psuedonym X signed DCO, but we do not\n> even know their real name\".\n\nI know it, and as I said before that gitgitgadget need PR creators to\nsign off user name of GitHub account, according to the DCO check. I can\nconfirmed that \"Aleen\" and \"Aleen 徐沛文\" are both the real name of mine,\nthe committer. I can use the account aleen42@vip.qq.com to confirm this.\n"},{"id":"441705","messageId":"tencent_97599A81311F56C0395EE209EC64CD485805@qq.com","threadId":"56877","inReplyTo":"2ebb863f.246.17d37140518.Coremail.pwxu@coremail.cn","subject":"RE: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen","fromEmail":"aleen42@vip.qq.com","sentAt":"2021-11-19T07:25:12Z","receivedAt":"2021-11-19T07:48:29Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"> I know it, and as I said before that gitgitgadget need PR creators to\n> sign off user name of GitHub account, according to the DCO check. I can\n> confirmed that \"Aleen\" and \"Aleen 徐沛文\" are both the real name of mine,\n> the committer. I can use the account aleen42@vip.qq.com to confirm this.\n\nConfirmed."},{"id":"441712","messageId":"877e2b84-347d-8687-d3dc-6c7ce508ac1d@gmail.com","threadId":"56877","inReplyTo":"96d8573dc808bc32990842675ca32a0d1e8a8cef.1637232636.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Bagas Sanjaya","fromEmail":"bagasdotme@gmail.com","sentAt":"2021-11-19T10:33:35Z","receivedAt":"2021-11-19T10:33:47Z","isPatch":true,"sender":{"key":"bagasdotme@gmail.com","avatar":"https://avatars.githubusercontent.com/u/40219486?v=4"},"body":"On 18/11/21 17.50, Aleen via GitGitGadget wrote:\n> +test_expect_success 'still output error with --empty when meeting empty files' '\n> +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n> +\techo Patch format detection failed. >expected &&\n> +\ttest_cmp expected actual\n> +'\n\nWhy isn't the echo string quoted?\n\n-- \nAn old man doll... just what I always wanted! - Clara\n"},{"id":"441718","messageId":"211119.861r3c4a4g.gmgdl@evledraar.gmail.com","threadId":"56877","inReplyTo":"877e2b84-347d-8687-d3dc-6c7ce508ac1d@gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-19T12:10:52Z","receivedAt":"2021-11-19T12:12:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Nov 19 2021, Bagas Sanjaya wrote:\n\n> On 18/11/21 17.50, Aleen via GitGitGadget wrote:\n>> +test_expect_success 'still output error with --empty when meeting empty files' '\n>> +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n>> +\techo Patch format detection failed. >expected &&\n>> +\ttest_cmp expected actual\n>> +'\n>\n> Why isn't the echo string quoted?\n\nThere's no need to quote the arguments given to echo in cases like\nthese.\n\nIt's not the same, i.e. it'll get N arguments on argv and not on, but it\nmakes it easier to spot things that do need quoting, rather than\nover-quoting everything.\n"},{"id":"441719","messageId":"CAPig+cQq3Ek7RL9NKuvR5V9OVULRf7=N3QEYvy9=xqdZB3EEWg@mail.gmail.com","threadId":"56877","inReplyTo":"211119.861r3c4a4g.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-11-19T12:20:24Z","receivedAt":"2021-11-19T12:20:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Nov 19, 2021 at 7:12 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> On Fri, Nov 19 2021, Bagas Sanjaya wrote:\n> > On 18/11/21 17.50, Aleen via GitGitGadget wrote:\n> >> +test_expect_success 'still output error with --empty when meeting empty files' '\n> >> +    test_must_fail git am --empty=drop empty.patch 2>actual &&\n> >> +    echo Patch format detection failed. >expected &&\n> >> +    test_cmp expected actual\n> >> +'\n> >\n> > Why isn't the echo string quoted?\n>\n> There's no need to quote the arguments given to echo in cases like\n> these.\n>\n> It's not the same, i.e. it'll get N arguments on argv and not on, but it\n> makes it easier to spot things that do need quoting, rather than\n> over-quoting everything.\n\nI recently expressed an opposing opinion in [1], stating effectively\nthat omitting the quotes like this is \"an accident waiting to happen\":\n\n    ... the lack of quotes ... in the `echo ... >expect` statement\n    gives me a moment's pause since it relies upon the fact that\n    `echo` will insert exactly one space between the ... arguments\n    (which happens to match the single space in the [command's output]\n    ). For clarity and that extra bit of robustness, I'd probably have\n    used a single double-quoted string argument with `echo`.\n\nBut, it's a fairly minor objection.\n\n[1]: https://lore.kernel.org/git/CAPig+cQVSUg1aqry_hMydJ=Uo=-VhOog6TUTpG=0on0LUcw8Dg@mail.gmail.com/\n"},{"id":"441757","messageId":"xmqq35nsrt4l.fsf@gitster.g","threadId":"56877","inReplyTo":"877e2b84-347d-8687-d3dc-6c7ce508ac1d@gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T16:46:02Z","receivedAt":"2021-11-19T16:46:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bagas Sanjaya <bagasdotme@gmail.com> writes:\n\n> On 18/11/21 17.50, Aleen via GitGitGadget wrote:\n>> +test_expect_success 'still output error with --empty when meeting empty files' '\n>> +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n>> +\techo Patch format detection failed. >expected &&\n>> +\ttest_cmp expected actual\n>> +'\n>\n> Why isn't the echo string quoted?\n\nYou may doing so without realizing, but such a \"why does the code\nnot do X\" question, especially without stating why you think the\ncode should do X, gets irritating quickly.\n\n    I think it is better to quote the arguments to echo to make it a\n    single string, because ...\n\nwould have helped the author of the patch.\n\nIf this were one-off, I would have let it go, but I thought it may\nbe worth commenting, as you seem to often ask this kind of\n\"questions\".\n"},{"id":"441758","messageId":"xmqqy25kqef0.fsf@gitster.g","threadId":"56877","inReplyTo":"CAPig+cQq3Ek7RL9NKuvR5V9OVULRf7=N3QEYvy9=xqdZB3EEWg@mail.gmail.com","subject":"Re: [PATCH v6 2/3] am: support --empty option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T16:49:07Z","receivedAt":"2021-11-19T16:49:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> I recently expressed an opposing opinion in [1], stating effectively\n> that omitting the quotes like this is \"an accident waiting to happen\":\n>\n>     ... the lack of quotes ... in the `echo ... >expect` statement\n>     gives me a moment's pause since it relies upon the fact that\n>     `echo` will insert exactly one space between the ... arguments\n>     (which happens to match the single space in the [command's output]\n>     ). For clarity and that extra bit of robustness, I'd probably have\n>     used a single double-quoted string argument with `echo`.\n>\n> But, it's a fairly minor objection.\n\nIt indeed is minor enough that a patch to turn an existing\n\n\techo A B C >expect &&\n\ttest_cmp expect actual\n\ninto\n\n\techo \"A B C\" >expect &&\n\ttest_cmp expect actual\n\nis not welcome.  But it still is worth pointing out and correcting\nin a patch to add new code, I would think.  It all depends on what\nwe care about, and the use of test_cmp means we do care about exact\nshape of the string, including the inter-word spacing.\n\nThanks.\n"},{"id":"441760","messageId":"xmqqr1bcqe6p.fsf@gitster.g","threadId":"56877","inReplyTo":"2ebb863f.246.17d37140518.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T16:54:06Z","receivedAt":"2021-11-19T16:54:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aleen 徐沛文 <pwxu@coremail.cn> writes:\n\n>> Yes.\n>> \n>> As the URL you were referred to explains, the sign-off procedure is\n>> to keep track of provenance of the code, which is a more \"legal\"\n>> formal requirement than just \"I use this pseudonym everywhere\".\n>> When a big company comes to us, claiming that \"this code is our\n>> intellectual property stolen from us\" and pointing at code added by\n>> a patch from you, we do not want to see us in the position to have\n>> to say \"eh, somebody who uses psuedonym X signed DCO, but we do not\n>> even know their real name\".\n>\n> I know it, and as I said before that gitgitgadget need PR creators to\n> sign off user name of GitHub account, according to the DCO check. I can\n> confirmed that \"Aleen\" and \"Aleen 徐沛文\" are both the real name of mine,\n> the committer. I can use the account aleen42@vip.qq.com to confirm this.\n\nCan somebody from GGG land help this user?  I _think_ the easiest\nworkaround (other than not using GGG and sending e-mail in the old\nfashioned way) is to commit and sign-off under the real name, and\npush under whatever GitHub username to throw a GGG pull request,\nwhich GGG should be able to take, as I have seen users forward other\nauthors commits just fine.\n\n\n"},{"id":"441765","messageId":"6c267a9e.471.17d39318b2b.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqr1bcqe6p.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-19T17:14:47Z","receivedAt":"2021-11-19T17:15:16Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":" \n> Can somebody from GGG land help this user?  I _think_ the easiest\n> workaround (other than not using GGG and sending e-mail in the old\n> fashioned way) is to commit and sign-off under the real name, and\n> push under whatever GitHub username to throw a GGG pull request,\n> which GGG should be able to take, as I have seen users forward other\n> authors commits just fine.\n\nWhen it comes to GGG, I just want to know whether this is the only\nway to contribute to Git? I think we can directly use GitHub to\nrun the reviewing procedure, rather than sending emails in this old\nfashioned way, since that Git code has been maintained in GitHub.\nE-mails does well in playing the role as a notification way. They\nhelps nothing in reviewing code, especially when we need to point out\nproblems or suggestions referred to certain code snippets.\n"},{"id":"441775","messageId":"xmqqpmqwosmd.fsf@gitster.g","threadId":"56877","inReplyTo":"6c267a9e.471.17d39318b2b.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T19:25:14Z","receivedAt":"2021-11-19T19:25:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aleen 徐沛文 <pwxu@coremail.cn> writes:\n\n>> Can somebody from GGG land help this user?  I _think_ the easiest\n>> workaround (other than not using GGG and sending e-mail in the old\n>> fashioned way) is to commit and sign-off under the real name, and\n>> push under whatever GitHub username to throw a GGG pull request,\n>> which GGG should be able to take, as I have seen users forward other\n>> authors commits just fine.\n>\n> When it comes to GGG, I just want to know whether this is the only\n> way to contribute to Git? I think we can directly use GitHub to\n> run the reviewing procedure, rather than sending emails in this old\n> fashioned way, since that Git code has been maintained in GitHub.\n\nNo, we do not develop or maintain at GitHub at all.  The repository\nat GitHub is one of the several publishing point and development is\ndone here.\n"},{"id":"441802","messageId":"xmqqtug7oj4g.fsf@gitster.g","threadId":"56877","inReplyTo":"9e60e77c041bca28a50d706c865eb776e5fe2ec2.1637298299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 2/2] am: support --empty=<option> to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T22:50:23Z","receivedAt":"2021-11-19T22:50:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n>\n> Since that the command 'git-format-patch' can include patches of\n> commits that emit no changes, the 'git-am' command should also\n> support an option, named as '--empty', to specify how to handle\n> those empty patches. In this commit, we have implemented three\n> valid options ('die', 'drop' and 'keep').\n>\n> Signed-off-by: Aleen <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-am.txt |  8 ++++++\n>  builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n>  t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n\n>  po/bg.po                 |  2 +-\n>  po/ca.po                 |  2 +-\n>  po/de.po                 |  2 +-\n>  po/el.po                 |  2 +-\n>  po/es.po                 |  2 +-\n>  po/fr.po                 |  2 +-\n>  po/git.pot               |  2 +-\n>  po/id.po                 |  2 +-\n>  po/it.po                 |  2 +-\n>  po/ko.po                 |  2 +-\n>  po/pl.po                 |  2 +-\n>  po/pt_PT.po              |  8 +++---\n>  po/ru.po                 |  4 +--\n>  po/sv.po                 |  2 +-\n>  po/tr.po                 |  2 +-\n>  po/vi.po                 |  2 +-\n>  po/zh_CN.po              |  9 +++++--\n>  po/zh_TW.po              |  7 ++++-\n\nPlease do not touch these po/ files.  They will be updated when i18n\ncoordinator decides to update the translation.\n\n"},{"id":"441821","messageId":"xmqqpmqvoico.fsf@gitster.g","threadId":"56877","inReplyTo":"9e60e77c041bca28a50d706c865eb776e5fe2ec2.1637298299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 2/2] am: support --empty=<option> to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-19T23:07:03Z","receivedAt":"2021-11-19T23:07:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +test_expect_success 'An empty input file is error regardless of --empty option' '\n\nTitles of all the other tests seem to begin with lowercase, so \"An\nempty\" -> \"an empty\", probably.\n\n> +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n> +\techo Patch format detection failed. >expected &&\n\nQuote for exactness, like the next test does.\n\n    echo \"Patch format detection failed.\" >expected\n\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'invalid when passing the --empty option alone' '\n> +\tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n> +\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n> +\ttest_cmp expected err\n> +'\n\nThis mode of failure of \"am\" may not leave \".git/rebase-apply\"\nbehind right now, or it may leave one in the future.  We do not want\nto worry about left-over cruft to interfere with the next test, so\ncleaning after ourselves with test_when_finished would be a good\nidea, i.e.\n\n    test_expect_success 'invalid when passing the --empty option alone' '\n            test_when_finished \"git am --abort || :\" &&\n            git checkout empty-commit^ &&\n            test_must_fail git am --empty empty-commit.patch 2>err &&\n            echo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n            test_cmp expected err\n    '\n\n> +test_expect_success 'a message without a patch is an error (default)' '\n> +\ttest_when_finished \"git am --abort || :\" &&\n> +\ttest_must_fail git am empty-commit.patch >err &&\n> +\tgrep \"Patch is empty\" err &&\n> +\trm -fr .git/rebase-apply\n> +'\n\nAnd the point of test_when_finished is to run the clean-up even when\nother steps in the test fails.  For example, \"test_must_fail git am\"\nmay fail to fail for any reason.  Or \"grep\" after it may fail.\nBecause the pieces in a single test is strung together with &&, any\nsuch failure means the control would NOT reach \"rm -fr\".  \n\nSince we have test_when_finished that cleans up after ourselves even\nin such a case, the last \"rm -fr\" step is unnecessary.\n\n> +test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n> +\ttest_when_finished \"git am --abort || :\" &&\n> +\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n> +\tgrep \"Patch is empty.\" err &&\n> +\trm -fr .git/rebase-apply\n> +'\n\nLikewise.\n\nOther than that (and po/ that should not be part of this patch),\nthings are looking good.\n\nThanks.\n"},{"id":"441907","messageId":"5d98a088e143c59ab26c98af4f888b789ac29c59.1637563611.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v8.git.1637563611.gitgitgadget@gmail.com","subject":"[PATCH v8 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T06:46:50Z","receivedAt":"2021-11-22T06:46:57Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441908","messageId":"pull.1076.v8.git.1637563611.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v7.git.1637298298.gitgitgadget@gmail.com","subject":"[PATCH v8 0/2] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T06:46:49Z","receivedAt":"2021-11-22T06:46:59Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. update code according to the seen branch\n 2. fix wrong document of git-am\n\n----------------------------------------------------------------------------\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n\n Documentation/git-am.txt           |  8 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 55 +++++++++++++++++++++++++++---\n t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++\n 4 files changed, 112 insertions(+), 6 deletions(-)\n\n\nbase-commit: ca35af825273b98fc8dc11527488952f5db8eb80\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v8\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v8\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v7:\n\n 1:  d612bc49d57 ! 1:  5d98a088e14 doc: git-format-patch: describe the option --always\n     @@ Commit message\n          This commit has described how to use '--always' option in the command\n          'git-format-patch' to include patches for commits that emit no changes.\n      \n     -    Signed-off-by: Aleen <aleen42@vip.qq.com>\n     +    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n      \n       ## Documentation/git-format-patch.txt ##\n      @@ Documentation/git-format-patch.txt: SYNOPSIS\n 2:  9e60e77c041 ! 2:  3ff18e16a7a am: support --empty=<option> to handle empty patches\n     @@ Commit message\n          those empty patches. In this commit, we have implemented three\n          valid options ('die', 'drop' and 'keep').\n      \n     -    Signed-off-by: Aleen <aleen42@vip.qq.com>\n     +    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n      \n       ## Documentation/git-am.txt ##\n      @@ Documentation/git-am.txt: SYNOPSIS\n     @@ Documentation/git-am.txt: OPTIONS\n       --quoted-cr=<action>::\n       \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n     -+--empty-commit=(die|drop|keep)::\n     ++--empty=(die|drop|keep)::\n      +\tBy default, or when the option is set to 'die', the command\n      +\terrors out on an input e-mail message that lacks a patch. When\n      +\tthis option is set to 'drop', skip such an e-mail message instead.\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t\tN_(\"(internal use for git-rebase)\")),\n       \t\tOPT_END()\n      \n     - ## po/bg.po ##\n     -@@ po/bg.po: msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"\"\n     - \"Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Прилагане: %.*s\"\n     -\n     - ## po/ca.po ##\n     -@@ po/ca.po: msgstr \"no s'ha pogut escriure el fitxer d'índex\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Índex brut: no es poden aplicar pedaços (bruts: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"S'està aplicant: %.*s\"\n     -\n     - ## po/de.po ##\n     -@@ po/de.po: msgstr \"Konnte Index-Datei nicht schreiben.\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Geänderter Index: kann Patches nicht anwenden (geändert: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Wende an: %.*s\"\n     -\n     - ## po/el.po ##\n     -@@ po/el.po: msgstr \"Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"\"\n     - \n     --#: builtin/am.c:1749 builtin/am.c:1817\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"\"\n     -\n     - ## po/es.po ##\n     -@@ po/es.po: msgstr \"no es posible escribir el archivo índice\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Índice sucio: no se puede aplicar parches (sucio: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Aplicando: %.*s\"\n     -\n     - ## po/fr.po ##\n     -@@ po/fr.po: msgstr \"impossible d'écrire le fichier d'index\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Index sale : impossible d'appliquer des patchs (sales : %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Application de  %.*s\"\n     -\n     - ## po/git.pot ##\n     -@@ po/git.pot: msgstr \"\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"\"\n     -\n     - ## po/id.po ##\n     -@@ po/id.po: msgstr \"tidak dapat menulis berkas indeks\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Menerapkan: %.*s\"\n     -\n     - ## po/it.po ##\n     -@@ po/it.po: msgstr \"impossibile scrivere il file indice\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Indice sporco: impossibile applicare le patch (elemento sporco: %s)\"\n     - \n     --#: builtin/am.c:1761 builtin/am.c:1829\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Applicazione in corso: %.*s\"\n     -\n     - ## po/ko.po ##\n     -@@ po/ko.po: msgstr \"적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)\"\n     - \n     --#: builtin/am.c:1808 builtin/am.c:1879\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"적용하는 중: %.*s\"\n     -\n     - ## po/pl.po ##\n     -@@ po/pl.po: msgstr \"nie można zapisać pliku indeksu\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Brudny indeks: nie można zastosować łatek (brudny: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Stosowanie: %.*s\"\n     -\n     - ## po/pt_PT.po ##\n     -@@\n     - #   bisect                           |  bisetar\n     - #   blame                            |  blame\n     - #   blob object                      |  objeto-blob\n     --#   branch                           |  ramo \n     -+#   branch                           |  ramo\n     - #   bug                              |  bug\n     - #   bundle                           |  conjunto\n     - #   bypass                           |  desviar\n     -@@\n     - #   loose refs                       |  refs soltas\n     - #   mark                             |  marca\n     - #   master                           |  master\n     --#   merge                            |  junção \n     -+#   merge                            |  junção\n     - #   mergetag                         |  etiqueta-junção\n     - #   object                           |  objeto\n     - #   object database                  |  base dados de objeto\n     -@@\n     - #   token                            |  token\n     - #   unset                            |  desdefinir\n     - #   untrack                          |  desmonitorizar\n     --#   \n     -+#\n     - #\n     - msgid \"\"\n     - msgstr \"\"\n     -@@ po/pt_PT.po: msgstr \"incapaz escrever ficheiro de index\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Index sujo: incapaz aplicar patches (sujo: %s)\"\n     - \n     --#: builtin/am.c:1797 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"A aplicar: %.*s\"\n     -\n     - ## po/ru.po ##\n     -@@\n     - # SOME DESCRIPTIVE TITLE.\n     - # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n     - # This file is distributed under the same license as the PACKAGE package.\n     --# \n     -+#\n     - # Translators:\n     - # Alexander Golubev <fatzer2@gmail.com>, 2020\n     - # Dimitriy Ryazantcev <DJm00n@mail.ru>, 2014-2021\n     -@@ po/ru.po: msgstr \"не удалось записать индекс\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Индекс изменён: нельзя применять патчи (изменено: %s)\"\n     - \n     --#: builtin/am.c:1748 builtin/am.c:1816\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Применение: %.*s\"\n     -\n     - ## po/sv.po ##\n     -@@ po/sv.po: msgstr \"kan inte skriva indexfil\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Tillämpar: %.*s\"\n     -\n     - ## po/tr.po ##\n     -@@ po/tr.po: msgstr \"indeks dosyası yazılamıyor\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Kirli indeks: Yamalar uygulanamıyor (kirli: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Uygulanıyor: %.*s\"\n     -\n     - ## po/vi.po ##\n     -@@ po/vi.po: msgstr \"không thể ghi tập tin lưu mục lục\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"Áp dụng: %.*s\"\n     -\n     - ## po/zh_CN.po ##\n     -@@ po/zh_CN.po: msgstr \"坏的索引文件 sha1 签名\"\n     - msgid \"index uses %.4s extension, which we do not understand\"\n     - msgstr \"索引使用不被支持的 %.4s 扩展\"\n     - \n     --# \t\n     -+#\n     - #: read-cache.c:1834\n     - #, c-format\n     - msgid \"ignoring %.4s extension\"\n     -@@ po/zh_CN.po: msgstr \"无法写入索引文件\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"脏索引：不能应用补丁（脏文件：%s）\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"应用：%.*s\"\n     - \n     -+#: builtin/am.c:1818\n     -+#, c-format\n     -+msgid \"Skipping: %.*s\"\n     -+msgstr \"跳过：%.*s\"\n     -+\n     - #: builtin/am.c:1815\n     - msgid \"No changes -- Patch already applied.\"\n     - msgstr \"没有变更 —— 补丁已经应用过。\"\n     -\n     - ## po/zh_TW.po ##\n     -@@ po/zh_TW.po: msgstr \"無法寫入索引檔案\"\n     - msgid \"Dirty index: cannot apply patches (dirty: %s)\"\n     - msgstr \"髒索引：不能套用修補檔（髒檔案：%s）\"\n     - \n     --#: builtin/am.c:1798 builtin/am.c:1865\n     -+#: builtin/am.c:1834 builtin/am.c:1902\n     - #, c-format\n     - msgid \"Applying: %.*s\"\n     - msgstr \"套用：%.*s\"\n     - \n     -+#: builtin/am.c:1818\n     -+#, c-format\n     -+msgid \"Skipping: %.*s\"\n     -+msgstr \"忽略：%.*s\"\n     -+\n     - #: builtin/am.c:1815\n     - msgid \"No changes -- Patch already applied.\"\n     - msgstr \"沒有變更——修補檔已經套用過。\"\n     -\n       ## t/t4150-am.sh ##\n      @@ t/t4150-am.sh: test_expect_success setup '\n       \n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n       \tgit -C client am ../patch\n       '\n       \n     -+test_expect_success 'An empty input file is error regardless of --empty option' '\n     ++test_expect_success 'an empty input file is error regardless of --empty option' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n     -+\techo Patch format detection failed. >expected &&\n     ++\techo \"Patch format detection failed.\" >expected &&\n      +\ttest_cmp expected actual\n      +'\n      +\n      +test_expect_success 'invalid when passing the --empty option alone' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n      +\tgit checkout empty-commit^ &&\n      +\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n      +\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +test_expect_success 'a message without a patch is an error (default)' '\n      +\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n     -+\tgrep \"Patch is empty\" err &&\n     -+\trm -fr .git/rebase-apply\n     ++\tgrep \"Patch is empty\" err\n      +'\n      +\n      +test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n      +\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n     -+\tgrep \"Patch is empty.\" err &&\n     -+\trm -fr .git/rebase-apply\n     ++\tgrep \"Patch is empty.\" err\n      +'\n      +\n      +test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n\n-- \ngitgitgadget\n"},{"id":"441909","messageId":"3ff18e16a7a24fdd8c57b40960e0d6b95c2490e6.1637563611.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v8.git.1637563611.gitgitgadget@gmail.com","subject":"[PATCH v8 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T06:46:51Z","receivedAt":"2021-11-22T06:47:04Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441910","messageId":"pull.1076.v9.git.1637564554.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v8.git.1637563611.gitgitgadget@gmail.com","subject":"[PATCH v9 0/2] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:02:32Z","receivedAt":"2021-11-22T07:02:39Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\nAleen 徐沛文 (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n\n Documentation/git-am.txt           |  8 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 55 +++++++++++++++++++++++++++---\n t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++\n 4 files changed, 112 insertions(+), 6 deletions(-)\n\n\nbase-commit: ca35af825273b98fc8dc11527488952f5db8eb80\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v9\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v9\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v8:\n\n 1:  5d98a088e14 ! 1:  3b41ca3dec7 doc: git-format-patch: describe the option --always\n     @@\n       ## Metadata ##\n     -Author: Aleen <aleen42@vip.qq.com>\n     +Author: Aleen 徐沛文 <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          doc: git-format-patch: describe the option --always\n 2:  3ff18e16a7a ! 2:  d2ec18b36af am: support --empty=<option> to handle empty patches\n     @@\n       ## Metadata ##\n     -Author: Aleen <aleen42@vip.qq.com>\n     +Author: Aleen 徐沛文 <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          am: support --empty=<option> to handle empty patches\n\n-- \ngitgitgadget\n"},{"id":"441911","messageId":"3b41ca3dec748a8467c99f051e9dfcca1091d6ee.1637564555.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v9.git.1637564554.gitgitgadget@gmail.com","subject":"[PATCH v9 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen 徐沛文 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:02:33Z","receivedAt":"2021-11-22T07:02:40Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441912","messageId":"d2ec18b36afab430287882aed4528a8efc90508a.1637564555.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v9.git.1637564554.gitgitgadget@gmail.com","subject":"[PATCH v9 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen 徐沛文 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:02:34Z","receivedAt":"2021-11-22T07:02:43Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441913","messageId":"4390f9f1.21d.17d4675b43d.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"d2ec18b36afab430287882aed4528a8efc90508a.1637564555.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v9 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-22T07:04:18Z","receivedAt":"2021-11-22T07:04:28Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> Can somebody from GGG land help this user?  I _think_ the easiest\n> workaround (other than not using GGG and sending e-mail in the old\n> fashioned way) is to commit and sign-off under the real name, and\n> push under whatever GitHub username to throw a GGG pull request,\n> which GGG should be able to take, as I have seen users forward other\n> authors commits just fine.\n\nSorry for the wrong report, and I have checked that the DCO integrated by GGG has only checked\nwhether the committer name is the same as the signed name. So I have changed it, and re-submitted\nit via version 9.\n\n> `--empty-commit=(die|drop|keep)::`\n\nBesides, I have also fixed a missed mistake, which also exists in the `seen` branch."},{"id":"441915","messageId":"xmqqbl2ck6u1.fsf@gitster.g","threadId":"56877","inReplyTo":"3ff18e16a7a24fdd8c57b40960e0d6b95c2490e6.1637563611.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 2/2] am: support --empty=<option> to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-22T07:06:14Z","receivedAt":"2021-11-22T07:06:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Aleen <aleen42@vip.qq.com>\n>\n> Since that the command 'git-format-patch' can include patches of\n> commits that emit no changes, the 'git-am' command should also\n> support an option, named as '--empty', to specify how to handle\n> those empty patches. In this commit, we have implemented three\n> valid options ('die', 'drop' and 'keep').\n>\n> Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n\nPerhaps this line should imitate what Hans Krentel did in\nhttps://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com/,\ni.e. real name first, and then (nickname) in parentheses.\n\nAlso, the in-body \"From:\" line should match the sign off.\n\nI corrected what has been queued in 'seen' manually when I applied\nthe previous round.\n\n> +--empty=(die|drop|keep)::\n\nThis is the only change relative to what is queued (we had\n\"--empty-commit\", which is remnant from an earlier iteration), and\nit makes the documentation consistent with what the code does.\n\nGood.\n"},{"id":"441916","messageId":"775978a8.236.17d46836f86.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqbl2ck6u1.fsf@gitster.g","subject":"Re: [PATCH v8 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-22T07:19:18Z","receivedAt":"2021-11-22T07:19:23Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"\n> Perhaps this line should imitate what Hans Krentel did in\n> https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com/,\n> i.e. real name first, and then (nickname) in parentheses.\n> \n> Also, the in-body \"From:\" line should match the sign off.\n> \n> I corrected what has been queued in 'seen' manually when I applied\n> the previous round.\n> \n> > +--empty=(die|drop|keep)::\n> \n> This is the only change relative to what is queued (we had\n> \"--empty-commit\", which is remnant from an earlier iteration), and\n> it makes the documentation consistent with what the code does.\n> \n> Good.\n\nIt seems there is something wrong when submitting via \"徐沛文 (Aleen) <aleen42@vip.qq.com>\"."},{"id":"441918","messageId":"59bce7131dab858e8c87944ccb02eae8ba5fd459.1637567471.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v10.git.1637567471.gitgitgadget@gmail.com","subject":"[PATCH v10 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:51:10Z","receivedAt":"2021-11-22T07:51:21Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"441920","messageId":"pull.1076.v10.git.1637567471.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v9.git.1637564554.gitgitgadget@gmail.com","subject":"[PATCH v10 0/2] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:51:09Z","receivedAt":"2021-11-22T07:51:22Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\ncc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:\nAleen 徐沛文 pwxu@coremail.cn\n\nAleen (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n\n Documentation/git-am.txt           |  8 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 55 +++++++++++++++++++++++++++---\n t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++\n 4 files changed, 112 insertions(+), 6 deletions(-)\n\n\nbase-commit: ca35af825273b98fc8dc11527488952f5db8eb80\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v10\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v10\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v9:\n\n 1:  3b41ca3dec7 ! 1:  59bce7131da doc: git-format-patch: describe the option --always\n     @@\n       ## Metadata ##\n     -Author: Aleen 徐沛文 <aleen42@vip.qq.com>\n     +Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          doc: git-format-patch: describe the option --always\n     @@ Commit message\n          This commit has described how to use '--always' option in the command\n          'git-format-patch' to include patches for commits that emit no changes.\n      \n     -    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n     +    Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n       ## Documentation/git-format-patch.txt ##\n      @@ Documentation/git-format-patch.txt: SYNOPSIS\n 2:  d2ec18b36af ! 2:  5025ad30ba7 am: support --empty=<option> to handle empty patches\n     @@\n       ## Metadata ##\n     -Author: Aleen 徐沛文 <aleen42@vip.qq.com>\n     +Author: Aleen <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          am: support --empty=<option> to handle empty patches\n     @@ Commit message\n          those empty patches. In this commit, we have implemented three\n          valid options ('die', 'drop' and 'keep').\n      \n     -    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>\n     +    Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n       ## Documentation/git-am.txt ##\n      @@ Documentation/git-am.txt: SYNOPSIS\n\n-- \ngitgitgadget\n"},{"id":"441919","messageId":"5025ad30ba7e4ebdec8a961d3f040b2038f98153.1637567471.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v10.git.1637567471.gitgitgadget@gmail.com","subject":"[PATCH v10 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-22T07:51:11Z","receivedAt":"2021-11-22T07:51:25Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"From: Aleen <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"441931","messageId":"nycvar.QRO.7.76.6.2111221256040.63@tvgsbejvaqbjf.bet","threadId":"56877","inReplyTo":"xmqqr1bcqe6p.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-11-22T11:57:27Z","receivedAt":"2021-11-22T11:57:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio & Aleen,\n\nOn Fri, 19 Nov 2021, Junio C Hamano wrote:\n\n> Aleen 徐沛文 <pwxu@coremail.cn> writes:\n>\n> >> Yes.\n> >>\n> >> As the URL you were referred to explains, the sign-off procedure is\n> >> to keep track of provenance of the code, which is a more \"legal\"\n> >> formal requirement than just \"I use this pseudonym everywhere\".\n> >> When a big company comes to us, claiming that \"this code is our\n> >> intellectual property stolen from us\" and pointing at code added by\n> >> a patch from you, we do not want to see us in the position to have\n> >> to say \"eh, somebody who uses psuedonym X signed DCO, but we do not\n> >> even know their real name\".\n> >\n> > I know it, and as I said before that gitgitgadget need PR creators to\n> > sign off user name of GitHub account, according to the DCO check. I can\n> > confirmed that \"Aleen\" and \"Aleen 徐沛文\" are both the real name of mine,\n> > the committer. I can use the account aleen42@vip.qq.com to confirm this.\n>\n> Can somebody from GGG land help this user?  I _think_ the easiest\n> workaround (other than not using GGG and sending e-mail in the old\n> fashioned way) is to commit and sign-off under the real name, and\n> push under whatever GitHub username to throw a GGG pull request,\n> which GGG should be able to take, as I have seen users forward other\n> authors commits just fine.\n\nGitGitGadget looks at the author information, so you need to ensure that\nthe \"Author:\" line in the output of `git log` shows the desired name. If\nit does not, you need to configure `user.name` (and user.email)` as\ndesired, and then re-commit with `git commit --amend --reset-author`, then\nforce-push.\n\nCiao,\nJohannes\n"},{"id":"441932","messageId":"nycvar.QRO.7.76.6.2111221258440.63@tvgsbejvaqbjf.bet","threadId":"56877","inReplyTo":"59bce7131dab858e8c87944ccb02eae8ba5fd459.1637567471.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-11-22T12:00:31Z","receivedAt":"2021-11-22T12:00:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Aleen,\n\nOn Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:\n\n> From: Aleen <aleen42@vip.qq.com>\n\nFWIW this information comes from your commit, specifically from the\nauthor information recorded when you committed first. To re-set it in\nthese two patches, run something like this:\n\n\tgit config --global user.name \"徐沛文 (Aleen)\"\n\tgit rebase -x \"git commit --amend --no-edit --reset-author\" HEAD~2\n\nand then force-push to your branch.\n\nCiao,\nDscho\n"},{"id":"442079","messageId":"683ecd80.13.17d4a65cf94.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"nycvar.QRO.7.76.6.2111221258440.63@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-23T01:25:26Z","receivedAt":"2021-11-23T01:25:35Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> Hi Aleen,\n> \n> On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:\n> \n> > From: Aleen <aleen42@vip.qq.com>\n> \n> FWIW this information comes from your commit, specifically from the\n> author information recorded when you committed first. To re-set it in\n> these two patches, run something like this:\n> \n> \tgit config --global user.name \"徐沛文 (Aleen)\"\n> \tgit rebase -x \"git commit --amend --no-edit --reset-author\" HEAD~2\n> \n> and then force-push to your branch.\n> \n> Ciao,\n> Dscho\n\nThe main problem is that it broke when I tried to submit via GGG:\nhttps://github.com/gitgitgadget/git/pull/1076#issuecomment-976087421"},{"id":"442142","messageId":"nycvar.QRO.7.76.6.2111231330000.63@tvgsbejvaqbjf.bet","threadId":"56877","inReplyTo":"683ecd80.13.17d4a65cf94.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-11-23T12:30:06Z","receivedAt":"2021-11-23T12:30:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Aleen,\n\nOn Tue, 23 Nov 2021, Aleen 徐沛文 wrote:\n\n> > Hi Aleen,\n> >\n> > On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:\n> >\n> > > From: Aleen <aleen42@vip.qq.com>\n> >\n> > FWIW this information comes from your commit, specifically from the\n> > author information recorded when you committed first. To re-set it in\n> > these two patches, run something like this:\n> >\n> > \tgit config --global user.name \"徐沛文 (Aleen)\"\n> > \tgit rebase -x \"git commit --amend --no-edit --reset-author\" HEAD~2\n> >\n> > and then force-push to your branch.\n> >\n> > Ciao,\n> > Dscho\n>\n> The main problem is that it broke when I tried to submit via GGG:\n> https://github.com/gitgitgadget/git/pull/1076#issuecomment-976087421\n\nWhoops. It seems that the `From:` header cannot be parsed by GitGitGadget.\nMost likely because the `<` and `>` of the email address are on the next\nline, not the same line as the `From:`.\n\nI had a quick look and opened\nhttps://github.com/gitgitgadget/gitgitgadget/pull/777 to fix this.\n\nCiao,\nDscho\n"},{"id":"442157","messageId":"pull.1076.v11.git.1637681215.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v10.git.1637567471.gitgitgadget@gmail.com","subject":"[PATCH v11 0/2] am: support --empty=(die|drop|keep) option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-23T15:26:52Z","receivedAt":"2021-11-23T15:27:00Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\ncc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:\nAleen 徐沛文 pwxu@coremail.cn\n\ncc: Aleen 徐沛文 pwxu@coremail.cn\n\n徐沛文 (Aleen) (2):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n\n Documentation/git-am.txt           |  8 +++++\n Documentation/git-format-patch.txt |  6 +++-\n builtin/am.c                       | 55 +++++++++++++++++++++++++++---\n t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++\n 4 files changed, 112 insertions(+), 6 deletions(-)\n\n\nbase-commit: f8b28837226f3932b867ca88a4f830bf203d2afe\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v11\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v11\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v10:\n\n 1:  59bce7131da ! 1:  3d7e96ce2b3 doc: git-format-patch: describe the option --always\n     @@\n       ## Metadata ##\n     -Author: Aleen <aleen42@vip.qq.com>\n     +Author: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          doc: git-format-patch: describe the option --always\n 2:  5025ad30ba7 ! 2:  6051ad9440a am: support --empty=<option> to handle empty patches\n     @@\n       ## Metadata ##\n     -Author: Aleen <aleen42@vip.qq.com>\n     +Author: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n       ## Commit message ##\n          am: support --empty=<option> to handle empty patches\n\n-- \ngitgitgadget\n"},{"id":"442158","messageId":"3d7e96ce2b32096a6b900abe825b9df7b0ec3831.1637681215.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v11.git.1637681215.gitgitgadget@gmail.com","subject":"[PATCH v11 1/2] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-23T15:26:53Z","receivedAt":"2021-11-23T15:27:03Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"442159","messageId":"6051ad9440a966124e9147ec344ee6d87c46944a.1637681215.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v11.git.1637681215.gitgitgadget@gmail.com","subject":"[PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-23T15:26:54Z","receivedAt":"2021-11-23T15:27:04Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"442162","messageId":"nycvar.QRO.7.76.6.2111231710500.63@tvgsbejvaqbjf.bet","threadId":"56877","inReplyTo":"3d7e96ce2b32096a6b900abe825b9df7b0ec3831.1637681215.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v11 1/2] doc: git-format-patch: describe the option --always","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-11-23T16:12:00Z","receivedAt":"2021-11-23T16:12:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 23 Nov 2021, 徐沛文 (Aleen) via GitGitGadget wrote:\n\n> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n>  <aleen42@vip.qq.com>\n\nI triggered the Azure Pipeline to submit this patch series after merging\nthe PR that fixed parsing of the `From:` line.\n\nIt looks a bit funny that this is in two lines, so I downloaded the mail\nfrom https://lore.kernel.org/git/ and fed it to `git am`, which was just\nfine with it.\n\nCiao,\nDscho\n"},{"id":"442203","messageId":"xmqqr1b67cp6.fsf@gitster.g","threadId":"56877","inReplyTo":"nycvar.QRO.7.76.6.2111231710500.63@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v11 1/2] doc: git-format-patch: describe the option --always","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-23T22:02:29Z","receivedAt":"2021-11-23T22:02:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Tue, 23 Nov 2021, 徐沛文 (Aleen) via GitGitGadget wrote:\n>\n>> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n>>  <aleen42@vip.qq.com>\n>\n> I triggered the Azure Pipeline to submit this patch series after merging\n> the PR that fixed parsing of the `From:` line.\n>\n> It looks a bit funny that this is in two lines, so I downloaded the mail\n> from https://lore.kernel.org/git/ and fed it to `git am`, which was just\n> fine with it.\n\n;-)  Thanks for double-checking carefully.\n\n\nThese two are identical to what has been in my tree (as I\nmanually tweaked the patches from the previous rounds).\n\n1:  6112004764 = 1:  e9181bd6fd doc: git-format-patch: describe the option --always\n2:  b26f121395 = 2:  ca426b823b am: support --empty=<option> to handle empty patches\n\nI'll pretend that I took v11 but please do not get alarmed if the\nauthor and committer times are from v10.\n\nThanks, both.\n"},{"id":"442373","messageId":"CABPp-BF-eYcJ7eS==VK3M2xOqf5z4KNK4dP6qd_UcfLO1NWBeg@mail.gmail.com","threadId":"56877","inReplyTo":"6051ad9440a966124e9147ec344ee6d87c46944a.1637681215.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-11-26T20:14:03Z","receivedAt":"2021-11-26T20:19:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Nov 23, 2021 at 8:38 AM 徐沛文 (Aleen) via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n>  <aleen42@vip.qq.com>\n>\n> Since that the command 'git-format-patch' can include patches of\n> commits that emit no changes, the 'git-am' command should also\n> support an option, named as '--empty', to specify how to handle\n> those empty patches. In this commit, we have implemented three\n> valid options ('die', 'drop' and 'keep').\n>\n> Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-am.txt |  8 ++++++\n>  builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n>  t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n>  3 files changed, 107 insertions(+), 5 deletions(-)\n>\n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index 0a4a984dfde..ba17063f621 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -16,6 +16,7 @@ SYNOPSIS\n>          [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n>          [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n>          [--quoted-cr=<action>]\n> +        [--empty=(die|drop|keep)]\n>          [(<mbox> | <Maildir>)...]\n>  'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n>\n> @@ -63,6 +64,13 @@ OPTIONS\n>  --quoted-cr=<action>::\n>         This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n>\n> +--empty=(die|drop|keep)::\n> +       By default, or when the option is set to 'die', the command\n> +       errors out on an input e-mail message that lacks a patch. When\n> +       this option is set to 'drop', skip such an e-mail message instead.\n> +       When this option is set to 'keep', create an empty commit,\n> +       recording the contents of the e-mail message as its log.\n\nWhat does 'errors out' mean?  Is the am operation aborted, and the\nuser return to the pre-am state?  Or is the am operation interrupted,\nwith the user being asked to choose whether to keep or drop the patch?\n Or something else (my first thought was \"Are you going to leave the\nindex locked?\")?  This description is not that clear.  To me, the\nwording suggests aborted (or worse), but what you actually implemented\nwas an interrupt-and-ask.\n\nCan I suggest using 'ask' instead of 'die'?  I think that will be\nclearer, and it matches the term used by git rebase --empty.\n\nAlso, the only instructions given to the user when you interrupt\ninclude how to skip the patch, but I don't see anything for how to\nkeep it.  The instructions are:\n'''\nPatch is empty.\nWhen you have resolved this problem, run \"git am --continue\".\nIf you prefer to skip this patch, run \"git am --skip\" instead.\nTo restore the original branch and stop patching, run \"git am --abort\".\n'''\n\nI tried it manually, and it turns out \"git am --continue\" will just\nspit out basically the same message again:\n'''\nApplying: empty commit\nNo changes - did you forget to use 'git add'?\nIf there is nothing left to stage, chances are that something else\nalready introduced the same changes; you might want to skip this patch.\nWhen you have resolved this problem, run \"git am --continue\".\nIf you prefer to skip this patch, run \"git am --skip\" instead.\nTo restore the original branch and stop patching, run \"git am --abort\".\n'''\n\nAnd if I try to run `git commit --allow-empty` (which I happen to\nremember is the command suggested by `git rebase --empty=ask` when it\nstops), then I'm given an empty editor; it is not pre-populated with\nthe appropriate commit message.  Can the portion of the empty patch\ncorresponding to the commit message be added to .git/COMMIT_EDITMSG to\ncorrect that?  Also, can some extra words be printed before\ninterrupting to explain what to do when you want to keep the empty\ncommit?  Something like:\n\"\"\"\nThe current commit being applied is empty.  If you wish to commit it\nanyway, use:\n    git commit --allow-empty\n\"\"\"\n\n> +\n>  -m::\n>  --message-id::\n>         Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 8677ea2348a..cc6512275aa 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -87,6 +87,12 @@ enum show_patch_type {\n>         SHOW_PATCH_DIFF = 1,\n>  };\n>\n> +enum empty_action {\n> +       DIE_EMPTY_COMMIT = 0,  /* output errors */\n> +       DROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n> +       KEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n> +};\n> +\n>  struct am_state {\n>         /* state directory path */\n>         char *dir;\n> @@ -118,6 +124,7 @@ struct am_state {\n>         int message_id;\n>         int scissors; /* enum scissors_type */\n>         int quoted_cr; /* enum quoted_cr_action */\n> +       int empty_type; /* enum empty_action */\n>         struct strvec git_apply_opts;\n>         const char *resolvemsg;\n>         int committer_date_is_author_date;\n> @@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n>         return 0;\n>  }\n>\n> +static int am_option_parse_empty(const struct option *opt,\n> +                                    const char *arg, int unset)\n> +{\n> +       int *opt_value = opt->value;\n> +\n> +       BUG_ON_OPT_NEG(unset);\n> +\n> +       if (!strcmp(arg, \"die\"))\n> +               *opt_value = DIE_EMPTY_COMMIT;\n> +       else if (!strcmp(arg, \"drop\"))\n> +               *opt_value = DROP_EMPTY_COMMIT;\n> +       else if (!strcmp(arg, \"keep\"))\n> +               *opt_value = KEEP_EMPTY_COMMIT;\n> +       else\n> +               return error(_(\"Invalid value for --empty: %s\"), arg);\n> +\n> +       return 0;\n> +}\n> +\n>  /**\n>   * Returns path relative to the am_state directory.\n>   */\n> @@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n>                 goto finish;\n>         }\n>\n> -       if (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> -               printf_ln(_(\"Patch is empty.\"));\n> -               die_user_resolve(state);\n> -       }\n> -\n>         strbuf_addstr(&msg, \"\\n\\n\");\n>         strbuf_addbuf(&msg, &mi.log_message);\n>         strbuf_stripspace(&msg, 0);\n> @@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n>         while (state->cur <= state->last) {\n>                 const char *mail = am_path(state, msgnum(state));\n>                 int apply_status;\n> +               int to_keep;\n>\n>                 reset_ident_date();\n>\n> @@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n>                 if (state->interactive && do_interactive(state))\n>                         goto next;\n>\n> +               to_keep = 0;\n> +               if (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> +                       switch (state->empty_type) {\n> +                       case DROP_EMPTY_COMMIT:\n> +                               say(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n> +                               goto next;\n> +                               break;\n> +                       case KEEP_EMPTY_COMMIT:\n> +                               to_keep = 1;\n> +                               break;\n> +                       case DIE_EMPTY_COMMIT:\n> +                               printf_ln(_(\"Patch is empty.\"));\n> +                               die_user_resolve(state);\n> +                               break;\n> +                       }\n> +               }\n> +\n>                 if (run_applypatch_msg_hook(state))\n>                         exit(1);\n> +               if (to_keep)\n> +                       goto commit;\n>\n>                 say(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n>\n> @@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n>                         die_user_resolve(state);\n>                 }\n>\n> +commit:\n>                 do_commit(state);\n>\n>  next:\n> @@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>                 { OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n>                   N_(\"GPG-sign commits\"),\n>                   PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n> +               OPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n> +                 N_(\"how to handle empty patches\"),\n> +                 PARSE_OPT_NONEG, am_option_parse_empty),\n>                 OPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n>                         N_(\"(internal use for git-rebase)\")),\n>                 OPT_END()\n> diff --git a/t/t4150-am.sh b/t/t4150-am.sh\n> index 2aaaa0d7ded..8c8bd4db220 100755\n> --- a/t/t4150-am.sh\n> +++ b/t/t4150-am.sh\n> @@ -196,6 +196,12 @@ test_expect_success setup '\n>\n>         git format-patch -M --stdout lorem^ >rename-add.patch &&\n>\n> +       git checkout -b empty-commit &&\n> +       git commit -m \"empty commit\" --allow-empty &&\n> +\n> +       : >empty.patch &&\n> +       git format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n> +\n>         # reset time\n>         sane_unset test_tick &&\n>         test_tick\n> @@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n>         git -C client am ../patch\n>  '\n>\n> +test_expect_success 'an empty input file is error regardless of --empty option' '\n> +       test_when_finished \"git am --abort || :\" &&\n> +       test_must_fail git am --empty=drop empty.patch 2>actual &&\n> +       echo \"Patch format detection failed.\" >expected &&\n> +       test_cmp expected actual\n> +'\n> +\n> +test_expect_success 'invalid when passing the --empty option alone' '\n> +       test_when_finished \"git am --abort || :\" &&\n> +       git checkout empty-commit^ &&\n> +       test_must_fail git am --empty empty-commit.patch 2>err &&\n> +       echo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n> +       test_cmp expected err\n> +'\n> +\n> +test_expect_success 'a message without a patch is an error (default)' '\n> +       test_when_finished \"git am --abort || :\" &&\n> +       test_must_fail git am empty-commit.patch >err &&\n> +       grep \"Patch is empty\" err\n> +'\n> +\n> +test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n> +       test_when_finished \"git am --abort || :\" &&\n> +       test_must_fail git am --empty=die empty-commit.patch >err &&\n> +       grep \"Patch is empty.\" err\n> +'\n> +\n> +test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n> +       git am --empty=drop empty-commit.patch >output &&\n> +       git rev-parse empty-commit^ >expected &&\n> +       git rev-parse HEAD >actual &&\n> +       test_cmp expected actual &&\n> +       grep \"Skipping: empty commit\" output\n> +'\n> +\n> +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n> +       git am --empty=keep empty-commit.patch &&\n> +       test_path_is_missing .git/rebase-apply &&\n> +       git show empty-commit --format=\"%s\" >expected &&\n> +       git show HEAD --format=\"%s\" >actual &&\n> +       test_cmp actual expected\n> +'\n> +\n>  test_done\n> --\n> gitgitgadget\n"},{"id":"442432","messageId":"36706841.2ec.17d6afdb326.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"CABPp-BF-eYcJ7eS==VK3M2xOqf5z4KNK4dP6qd_UcfLO1NWBeg@mail.gmail.com","subject":"Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-29T09:19:11Z","receivedAt":"2021-11-29T09:21:35Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"Dears Hamano,\n\n   Elijah Newren has given two better suggestions:\n\n       1. Use 'ask' rather than 'die'\n       2. When erroring out 'Patch is empty', print out a tutorial information\n          to help users using 'git commit --allow-empty' to keep recording as \n          an empty commit.\n\n   Should we continue to implement these features in current PR?\n\nAleen\n\n> > +--empty=(die|drop|keep)::\n> > +       By default, or when the option is set to 'die', the command\n> > +       errors out on an input e-mail message that lacks a patch. When\n> > +       this option is set to 'drop', skip such an e-mail message instead.\n> > +       When this option is set to 'keep', create an empty commit,\n> > +       recording the contents of the e-mail message as its log.\n> \n> What does 'errors out' mean?  Is the am operation aborted, and the\n> user return to the pre-am state?  Or is the am operation interrupted,\n> with the user being asked to choose whether to keep or drop the patch?\n>  Or something else (my first thought was \"Are you going to leave the\n> index locked?\")?  This description is not that clear.  To me, the\n> wording suggests aborted (or worse), but what you actually implemented\n> was an interrupt-and-ask.\n> \n> Can I suggest using 'ask' instead of 'die'?  I think that will be\n> clearer, and it matches the term used by git rebase --empty.\n> \n> Also, the only instructions given to the user when you interrupt\n> include how to skip the patch, but I don't see anything for how to\n> keep it.  The instructions are:\n> '''\n> Patch is empty.\n> When you have resolved this problem, run \"git am --continue\".\n> If you prefer to skip this patch, run \"git am --skip\" instead.\n> To restore the original branch and stop patching, run \"git am --abort\".\n> '''\n> \n> I tried it manually, and it turns out \"git am --continue\" will just\n> spit out basically the same message again:\n> '''\n> Applying: empty commit\n> No changes - did you forget to use 'git add'?\n> If there is nothing left to stage, chances are that something else\n> already introduced the same changes; you might want to skip this patch.\n> When you have resolved this problem, run \"git am --continue\".\n> If you prefer to skip this patch, run \"git am --skip\" instead.\n> To restore the original branch and stop patching, run \"git am --abort\".\n> '''\n> \n> And if I try to run `git commit --allow-empty` (which I happen to\n> remember is the command suggested by `git rebase --empty=ask` when it\n> stops), then I'm given an empty editor; it is not pre-populated with\n> the appropriate commit message.  Can the portion of the empty patch\n> corresponding to the commit message be added to .git/COMMIT_EDITMSG to\n> correct that?  Also, can some extra words be printed before\n> interrupting to explain what to do when you want to keep the empty\n> commit?  Something like:\n> \"\"\"\n> The current commit being applied is empty.  If you wish to commit it\n> anyway, use:\n>     git commit --allow-empty\n> \"\"\"\n"},{"id":"442434","messageId":"7cffa143.3af.17d6b2371c7.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"36706841.2ec.17d6afdb326.Coremail.pwxu@coremail.cn","subject":"Re: Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-29T10:00:24Z","receivedAt":"2021-11-29T10:02:32Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"It is quite complicated to support 'git commit --allow-empty' extracting messages\nfrom an empty patch, because 'git am' has to find somewhere to store the parsed state\nfor 'git commit' to read. How about directly supporting another interactive option\n'--allow-empty' for 'git am' to keep recording?\n\n> Dears Hamano,\n> \n>    Elijah Newren has given two better suggestions:\n> \n>        1. Use 'ask' rather than 'die'\n>        2. When erroring out 'Patch is empty', print out a tutorial information\n>           to help users using 'git commit --allow-empty' to keep recording as \n>           an empty commit.\n> \n>    Should we continue to implement these features in current PR?\n> \n> Aleen\n"},{"id":"442456","messageId":"xmqqo8623jyw.fsf@gitster.g","threadId":"56877","inReplyTo":"CABPp-BF-eYcJ7eS==VK3M2xOqf5z4KNK4dP6qd_UcfLO1NWBeg@mail.gmail.com","subject":"Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-29T18:17:11Z","receivedAt":"2021-11-29T18:19:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> +--empty=(die|drop|keep)::\n>> +       By default, or when the option is set to 'die', the command\n>> +       errors out on an input e-mail message that lacks a patch. When\n>> +       this option is set to 'drop', skip such an e-mail message instead.\n>> +       When this option is set to 'keep', create an empty commit,\n>> +       recording the contents of the e-mail message as its log.\n>\n> What does 'errors out' mean?  Is the am operation aborted, and the\n> user return to the pre-am state?  Or is the am operation interrupted,\n> with the user being asked to choose whether to keep or drop the patch?\n\nI think it is the same as how \"git am\" without this sees a piece of\ne-mail without any patch in it.  It exits with non-zero status, but\nkeeps the contents of .git/rebase-apply directory intact so that the\nuser can decide what the next action should be.  \"the command stops\nwith non-zero status and gives control back to the user\" might be a\nbetter explanation.\n\nAs to the name of the option's value, I think 'die' is a much better\nword than 'ask' for this behaviour.  It is not like we retain\ncontrol, give \"do you want to do X or Y?\"  prompt and do either X or\nY ourselves, which is what I expect out of 'ask'.  If we just exit\nwith non-zero value and give the control back to the user, then we\nare not asking.\n\nThis is a tangent, but in the current Documentation/git-am.txt, the\nphrasing for \"--skip\" and \"--resolved\" are a bit inconsistent.\n\n - The \"--skip\" operation calls the situation it is designed to handle\n   \"an aborted patch\".\n\n - The \"--resolved\" operation calls it \"a patch failure\".\n\nThe \"--abort\" operation interestingly does not use any word to\nexplain the situation it is designed to be used in.  It just\nassumes that the user knows \"git am\" session is somehow still in\neffect.\n\n\n"},{"id":"442486","messageId":"CABPp-BEOJs7E4Cn35UkzNAQ+Qp3me-ZPc36+LyaTnqi_RFXNAg@mail.gmail.com","threadId":"56877","inReplyTo":"7cffa143.3af.17d6b2371c7.Coremail.pwxu@coremail.cn","subject":"Re: Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-11-29T17:10:33Z","receivedAt":"2021-11-29T20:35:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 <pwxu@coremail.cn> wrote:\n>\n> It is quite complicated to support 'git commit --allow-empty' extracting messages\n> from an empty patch, because 'git am' has to find somewhere to store the parsed state\n> for 'git commit' to read.\n\n.git/COMMIT_EDITMSG is such a place that already exists, assuming you\nare just extracting a commit message.\n\n> How about directly supporting another interactive option\n> '--allow-empty' for 'git am' to keep recording?\n\n--empty=keep was already part of your patch series; I don't see why\nyou'd need to add a synonym (--allow-empty) for it.\n\nI think you were trying to ask if you could just leave out the\nimplementation of --empty=ask (or --empty=die) from your patches.\nThat's usually fine, but there is a small wrinkle here since it was\nyour chosen default.  You'll have to pick a different default, and\npossibly alert users in the documentation that the default may change\nin the future if/when the other option is implemented.\n\n> > Dears Hamano,\n> >\n> >    Elijah Newren has given two better suggestions:\n> >\n> >        1. Use 'ask' rather than 'die'\n> >        2. When erroring out 'Patch is empty', print out a tutorial information\n> >           to help users using 'git commit --allow-empty' to keep recording as\n> >           an empty commit.\n> >\n> >    Should we continue to implement these features in current PR?\n> >\n> > Aleen\n"},{"id":"442553","messageId":"CABPp-BHJq6L_1dG_YELrgO9M0w6qS2Jg3gDtpVWonj9i9wUR4Q@mail.gmail.com","threadId":"56877","inReplyTo":"xmqqo8623jyw.fsf@gitster.g","subject":"Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-11-29T18:57:22Z","receivedAt":"2021-11-29T22:58:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Nov 29, 2021 at 10:17 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> >> +--empty=(die|drop|keep)::\n> >> +       By default, or when the option is set to 'die', the command\n> >> +       errors out on an input e-mail message that lacks a patch. When\n> >> +       this option is set to 'drop', skip such an e-mail message instead.\n> >> +       When this option is set to 'keep', create an empty commit,\n> >> +       recording the contents of the e-mail message as its log.\n> >\n> > What does 'errors out' mean?  Is the am operation aborted, and the\n> > user return to the pre-am state?  Or is the am operation interrupted,\n> > with the user being asked to choose whether to keep or drop the patch?\n>\n> I think it is the same as how \"git am\" without this sees a piece of\n> e-mail without any patch in it.  It exits with non-zero status, but\n> keeps the contents of .git/rebase-apply directory intact so that the\n> user can decide what the next action should be.  \"the command stops\n> with non-zero status and gives control back to the user\" might be a\n> better explanation.\n>\n> As to the name of the option's value, I think 'die' is a much better\n> word than 'ask' for this behaviour.  It is not like we retain\n> control, give \"do you want to do X or Y?\"  prompt and do either X or\n> Y ourselves, which is what I expect out of 'ask'.  If we just exit\n> with non-zero value and give the control back to the user, then we\n> are not asking.\n\nWell, I still think 'die' implies aborting the entire overall `am`\noperation, and is thus a confusing term.  To me, 'stop' or 'interrupt'\nwould be better if you want a one-word term and don't like 'ask'.\n\nIf you don't like the term 'ask' here, perhaps we should also change\nrebase?  rebase --empty has 'ask' as a choice for\nstopping/interrupting the rebase operation and letting the user decide\nhow to handle it.  (There is a very subtly different context for\nrebase's --empty=ask, namely in that it is only triggered for patches\nthat were not originally empty but become so do the the new base the\npatches are being applied upon already having the changes from the\npatch in question, but that doesn't seem like a big enough difference\nto suggest it should have a different name.)\n\n\nThere's another bit to my query on the semantics, though.  The second\nhalf of my critique was that the current form of the series does not\ninform the user how to handle the situation when `git-am` stops.  It\nonly tells the user how to drop the patch.  (If there's only one\noption, why doesn't git just take it automatically?)  I think it\nshould also tell the user how to keep the patch.  Now, if both options\nare presented to the user when the operation stops, would that qualify\nas the user having been 'asked' what to do?\n"},{"id":"442603","messageId":"pull.1076.v12.git.1638250645.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v11.git.1637681215.gitgitgadget@gmail.com","subject":"[PATCH v12 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T05:37:22Z","receivedAt":"2021-11-30T05:37:30Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           | 14 +++++-\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 77 ++++++++++++++++++++++++++----\n t/t4150-am.sh                      | 61 +++++++++++++++++++++++\n t/t7512-status-help.sh             |  1 +\n wt-status.c                        |  3 ++\n 6 files changed, 151 insertions(+), 11 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v12\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v12\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v11:\n\n 1:  3d7e96ce2b3 = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  6051ad9440a = 2:  a3e850bab7d am: support --empty=<option> to handle empty patches\n -:  ----------- > 3:  d44dac09c87 am: support --allow-empty to record specific empty patches\n\n-- \ngitgitgadget\n"},{"id":"442604","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638250645.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v12.git.1638250645.gitgitgadget@gmail.com","subject":"[PATCH v12 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T05:37:23Z","receivedAt":"2021-11-30T05:37:31Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"442605","messageId":"a3e850bab7d8a38869dc1320ef2805e114b7aa3e.1638250645.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v12.git.1638250645.gitgitgadget@gmail.com","subject":"[PATCH v12 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T05:37:24Z","receivedAt":"2021-11-30T05:37:35Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"442606","messageId":"d44dac09c872257306d9718564820435e8a07ca2.1638250645.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v12.git.1638250645.gitgitgadget@gmail.com","subject":"[PATCH v12 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T05:37:25Z","receivedAt":"2021-11-30T05:37:36Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  6 +++++-\n builtin/am.c             | 24 +++++++++++++++++++-----\n t/t4150-am.sh            | 12 ++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 40 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex ba17063f621..fe3af32f7f7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -199,6 +199,10 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tKeep recording the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex cc6512275aa..2ae6fabb28a 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tto_keep = 1;\n \t\t\t\tbreak;\n \t\t\tcase DIE_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t\t\t\"If you want to keep recording it, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1898,10 +1899,15 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n+\tif (allow_empty) {\n+\t\tgoto commit;\n+\t}\n+\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n@@ -1928,6 +1934,7 @@ static void am_resolve(struct am_state *state)\n \n \trepo_rerere(the_repository, 0);\n \n+commit:\n \tdo_commit(state);\n \n next:\n@@ -2237,7 +2244,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2390,6 +2398,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"keep recording the empty patch as empty commits\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2498,7 +2509,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\t\tam_resolve(&state, 0);\n+\t\tbreak;\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, 1);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 8c8bd4db220..cab21411f00 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1201,4 +1201,16 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+\n+test_expect_success 'record as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to keep recording it, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..5513018154e 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to keep recording this empty patch)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d036f88a6b9 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to keep recording this empty patch)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"442607","messageId":"3a5c138f.208.17d6f601149.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"CABPp-BEOJs7E4Cn35UkzNAQ+Qp3me-ZPc36+LyaTnqi_RFXNAg@mail.gmail.com","subject":"Re: [PATCH v12 3/3] am: support --allow-empty to record specific empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-11-30T05:45:06Z","receivedAt":"2021-11-30T05:45:14Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"I have introduced an interactive option `--allow-empty` in a new independent commit,\nand it is not the synonym of the `--empty` option because it is only for the case in\na middle am session where users can resolve \"died\" problems by choosing whether to\nrecord current empty patch. The `--empty` option acts like a strategy option to tell\n`git-am` how to handle when meeting any empty patches.\n\n> On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 <pwxu@coremail.cn> wrote:\n> >\n> > It is quite complicated to support 'git commit --allow-empty' extracting messages\n> > from an empty patch, because 'git am' has to find somewhere to store the parsed state\n> > for 'git commit' to read.\n> \n> .git/COMMIT_EDITMSG is such a place that already exists, assuming you\n> are just extracting a commit message.\n> \n> > How about directly supporting another interactive option\n> > '--allow-empty' for 'git am' to keep recording?\n> \n> --empty=keep was already part of your patch series; I don't see why\n> you'd need to add a synonym (--allow-empty) for it.\n> \n> I think you were trying to ask if you could just leave out the\n> implementation of --empty=ask (or --empty=die) from your patches.\n> That's usually fine, but there is a small wrinkle here since it was\n> your chosen default.  You'll have to pick a different default, and\n> possibly alert users in the documentation that the default may change\n> in the future if/when the other option is implemented.\n> \n> > > Dears Hamano,\n> > >\n> > >    Elijah Newren has given two better suggestions:\n> > >\n> > >        1. Use 'ask' rather than 'die'\n> > >        2. When erroring out 'Patch is empty', print out a tutorial information\n> > >           to help users using 'git commit --allow-empty' to keep recording as\n> > >           an empty commit.\n> > >\n> > >    Should we continue to implement these features in current PR?\n> > >\n> > > Aleen"},{"id":"442613","messageId":"xmqq8rx62jn7.fsf@gitster.g","threadId":"56877","inReplyTo":"d44dac09c872257306d9718564820435e8a07ca2.1638250645.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 3/3] am: support --allow-empty to record specific empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-30T07:21:48Z","receivedAt":"2021-11-30T07:21:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"\"徐沛文 (Aleen)\" via GitGitGadget\"  <gitgitgadget@gmail.com>\nwrites:\n\n> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n>  <aleen42@vip.qq.com>\n>\n> This option helps to record specific empty patches in the middle\n> of an am session.\n>\n> Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-am.txt |  6 +++++-\n>  builtin/am.c             | 24 +++++++++++++++++++-----\n>  t/t4150-am.sh            | 12 ++++++++++++\n>  t/t7512-status-help.sh   |  1 +\n>  wt-status.c              |  3 +++\n>  5 files changed, 40 insertions(+), 6 deletions(-)\n>\n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index ba17063f621..fe3af32f7f7 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -18,7 +18,7 @@ SYNOPSIS\n>  \t [--quoted-cr=<action>]\n>  \t [--empty=(die|drop|keep)]\n>  \t [(<mbox> | <Maildir>)...]\n> -'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n> +'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -199,6 +199,10 @@ default.   You can use `--no-utf8` to override this.\n>  \tthe e-mail message; if `diff`, show the diff portion only.\n>  \tDefaults to `raw`.\n>  \n> +--allow-empty::\n> +\tKeep recording the empty patch as an empty commit with\n> +\tthe contents of the e-mail message as its log.\n> +\n>  DISCUSSION\n>  ----------\n>  \n> diff --git a/builtin/am.c b/builtin/am.c\n> index cc6512275aa..2ae6fabb28a 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n>  \t\t\t\tto_keep = 1;\n>  \t\t\t\tbreak;\n>  \t\t\tcase DIE_EMPTY_COMMIT:\n> -\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n> +\t\t\t\t\t\t\t\"If you want to keep recording it, run \\\"git am --allow-empty\\\".\"));\n\nAn overly deep indentation.  Make \"If\" align with \"Patch\",\nprobably.  Also \"to keep recording it\" -> \"to record it\".\n\n\"If you want to record the e-mail text as the log message of an\nempty commit\" may be a bit too much, but just saying \"record it\" may\nnot be clear enough to explain what gets recorded how.\n\n> @@ -1898,10 +1899,15 @@ next:\n>  /**\n>   * Resume the current am session after patch application failure. The user did\n>   * all the hard work, and we do not have to do any patch application. Just\n> - * trust and commit what the user has in the index and working tree.\n> + * trust and commit what the user has in the index and working tree. If `allow_empty`\n> + * is true, commit as an empty commit.\n>   */\n> -static void am_resolve(struct am_state *state)\n> +static void am_resolve(struct am_state *state, int allow_empty)\n>  {\n> +\tif (allow_empty) {\n> +\t\tgoto commit;\n> +\t}\n\nLose {} around a single statement block.\n\nBut more importantly, doesn't this break \"git am\" when the user\ngives \"--allow-empty\" in cases where you are not expecting?\n\nLike when the patch application (with or without -3) fails, either\nduring \"git am -i\" or \"git am\" without \"-i\"?\n\nThe parameter claims to be \"allow empty\", implying that the option\nis about allowing to end up in an empty commit (and depending on the\ninput, the resulting commit may not be empty and that is OK).  But\nwhat the code does is to _ignore_ the unmerged check, not recording\nthe manual resolution the user did for later reuse, and force a call\nto do_commit() fail if the index is still unmerged.\n\n>  \tvalidate_resume_state(state);\n>  \n>  \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n\nI suspect that the real code that wants to be changed is below this\nline, where we say \"if the index does not have changes (relative to\nthe HEAD), then complain and die\".  Perhaps it should become\nsomething along this line:\n\n\tif (!repo_index_has_changes(...)) {\n\t\tif (allow_empty) {\n\t\t\tprintf_ln(_(\"No changes - recording an empty commit.\");\n\t\t} else {\n\t\t\tprintf_ln(_(\"No changes - did you forget ...\"\n\t\t\t\t    \"...\"));\n\t\t\tdie_user_resolve(state);\n\t\t}\n\t}\n\ni.e. \"when the index does not have changes, tell the user that we'd\ncreate an empty commit, if allow-empty is in effect. otherwise,\ncomplain and die\".\n\nI suspect that the worst failure mode may be \"git am\" (without -3)\nfirst fails to apply a patch (hence the index is unchanged wrt\nHEAD), then \"git am\" that is run with \"--allow-empty\" ignores the\nfailure and creates an empty commit.  Even with the \"something along\nthis line\" change above, such a failure mode still exists.  You\nreally need to see if we had an input without any patch and do the\nskipping only when it is the case, I think.\n\n"},{"id":"442618","messageId":"pull.1076.v13.git.1638266115.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v12.git.1638250645.gitgitgadget@gmail.com","subject":"[PATCH v13 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T09:55:12Z","receivedAt":"2021-11-30T09:55:20Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           | 14 ++++-\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 82 +++++++++++++++++++++++++-----\n t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++\n t/t7512-status-help.sh             |  1 +\n wt-status.c                        |  3 ++\n 6 files changed, 164 insertions(+), 15 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v13\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v13\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v12:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  a3e850bab7d = 2:  a3e850bab7d am: support --empty=<option> to handle empty patches\n 3:  d44dac09c87 ! 3:  08bd397ae7a am: support --allow-empty to record specific empty patches\n     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n       \tDefaults to `raw`.\n       \n      +--allow-empty::\n     -+\tKeep recording the empty patch as an empty commit with\n     ++\tRecord the empty patch as an empty commit with\n      +\tthe contents of the e-mail message as its log.\n      +\n       DISCUSSION\n     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n       \t\t\tcase DIE_EMPTY_COMMIT:\n      -\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n     -+\t\t\t\t\t\t\t\"If you want to keep recording it, run \\\"git am --allow-empty\\\".\"));\n     ++\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n       \t\t\t\tdie_user_resolve(state);\n       \t\t\t\tbreak;\n       \t\t\t}\n     @@ builtin/am.c: next:\n        * all the hard work, and we do not have to do any patch application. Just\n      - * trust and commit what the user has in the index and working tree.\n      + * trust and commit what the user has in the index and working tree. If `allow_empty`\n     -+ * is true, commit as an empty commit.\n     ++ * is true, commit as an empty commit when there is no changes.\n        */\n      -static void am_resolve(struct am_state *state)\n      +static void am_resolve(struct am_state *state, int allow_empty)\n       {\n     -+\tif (allow_empty) {\n     -+\t\tgoto commit;\n     -+\t}\n     -+\n       \tvalidate_resume_state(state);\n       \n       \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n     -@@ builtin/am.c: static void am_resolve(struct am_state *state)\n     - \n     - \trepo_rerere(the_repository, 0);\n       \n     -+commit:\n     - \tdo_commit(state);\n     + \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n     +-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n     +-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n     +-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n     +-\t\tdie_user_resolve(state);\n     ++\t\tif (allow_empty)\n     ++\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n     ++\t\telse {\n     ++\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n     ++\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n     ++\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n     ++\t\t\tdie_user_resolve(state);\n     ++\t\t}\n     + \t}\n       \n     - next:\n     + \tif (unmerged_cache()) {\n      @@ builtin/am.c: enum resume_type {\n       \tRESUME_SKIP,\n       \tRESUME_ABORT,\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n       \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n      +\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n     -+\t\t\tN_(\"keep recording the empty patch as empty commits\"),\n     ++\t\t\tN_(\"record the empty patch as an empty commit\"),\n      +\t\t\tRESUME_ALLOW_EMPTY),\n       \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n       \t\t\t&state.committer_date_is_author_date,\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\tbreak;\n       \tcase RESUME_RESOLVED:\n      -\t\tam_resolve(&state);\n     -+\t\tam_resolve(&state, 0);\n     -+\t\tbreak;\n      +\tcase RESUME_ALLOW_EMPTY:\n     -+\t\tam_resolve(&state, 1);\n     ++\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n       \t\tbreak;\n       \tcase RESUME_SKIP:\n       \t\tam_skip(&state);\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n       \ttest_cmp actual expected\n       '\n       \n     ++test_expect_success 'skip an empty patch in the middle of an am session' '\n     ++\tgit checkout empty-commit^ &&\n     ++\ttest_must_fail git am empty-commit.patch >err &&\n     ++\tgrep \"Patch is empty.\" err &&\n     ++\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n     ++\tgit am --skip &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n     ++\tgit rev-parse empty-commit^ >expected &&\n     ++\tgit rev-parse HEAD >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n      +\n     -+test_expect_success 'record as an empty commit in the middle of an am session' '\n     ++test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n      +\tgit checkout empty-commit^ &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n      +\tgrep \"Patch is empty.\" err &&\n     -+\tgrep \"If you want to keep recording it, run \\\"git am --allow-empty\\\".\" err &&\n     ++\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n      +\tgit am --allow-empty &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n      +\tgit show empty-commit --format=\"%s\" >expected &&\n      +\tgit show HEAD --format=\"%s\" >actual &&\n      +\ttest_cmp actual expected\n     @@ t/t7512-status-help.sh: test_expect_success 'status in an am session: empty patc\n       On branch am_empty\n       You are in the middle of an am session.\n       The current patch is empty.\n     -+  (use \"git am --allow-empty\" to keep recording this empty patch)\n     ++  (use \"git am --allow-empty\" to record this patch as an empty commit)\n         (use \"git am --skip\" to skip this patch)\n         (use \"git am --abort\" to restore the original branch)\n       \n     @@ wt-status.c: static void show_am_in_progress(struct wt_status *s,\n       \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n      +\t\telse\n      +\t\t\tstatus_printf_ln(s, color,\n     -+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to keep recording this empty patch)\"));\n     ++\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n       \t\tstatus_printf_ln(s, color,\n       \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n       \t\tstatus_printf_ln(s, color,\n\n-- \ngitgitgadget\n"},{"id":"442619","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638266115.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v13.git.1638266115.gitgitgadget@gmail.com","subject":"[PATCH v13 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T09:55:13Z","receivedAt":"2021-11-30T09:55:26Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"442620","messageId":"a3e850bab7d8a38869dc1320ef2805e114b7aa3e.1638266115.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v13.git.1638266115.gitgitgadget@gmail.com","subject":"[PATCH v13 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T09:55:14Z","receivedAt":"2021-11-30T09:55:27Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  8 ++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..ba17063f621 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,13 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message that lacks a patch. When\n+\tthis option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"442621","messageId":"08bd397ae7a4e4320c00b85a4faf2d86ef29edab.1638266115.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v13.git.1638266115.gitgitgadget@gmail.com","subject":"[PATCH v13 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-11-30T09:55:15Z","receivedAt":"2021-11-30T09:55:30Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  6 +++++-\n builtin/am.c             | 29 ++++++++++++++++++++---------\n t/t4150-am.sh            | 24 ++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 53 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex ba17063f621..f2c5e3e37e0 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -199,6 +199,10 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tRecord the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex cc6512275aa..58c7638b9bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tto_keep = 1;\n \t\t\t\tbreak;\n \t\t\tcase DIE_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1898,19 +1899,24 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when there is no changes.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n-\t\tdie_user_resolve(state);\n+\t\tif (allow_empty)\n+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n+\t\telse {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tif (unmerged_cache()) {\n@@ -2237,7 +2243,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2390,6 +2397,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2498,7 +2508,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 8c8bd4db220..3e60460022f 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1201,4 +1201,28 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..9309becfe03 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d578a0e9192 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"442736","messageId":"pull.1076.v14.git.1638329848.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v13.git.1638266115.gitgitgadget@gmail.com","subject":"[PATCH v14 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-01T03:37:25Z","receivedAt":"2021-12-01T03:37:47Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           | 16 +++++-\n Documentation/git-format-patch.txt |  6 ++-\n builtin/am.c                       | 82 +++++++++++++++++++++++++-----\n t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++\n t/t7512-status-help.sh             |  1 +\n wt-status.c                        |  3 ++\n 6 files changed, 166 insertions(+), 15 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v14\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v14\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v13:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  a3e850bab7d ! 2:  b6a04fc12df am: support --empty=<option> to handle empty patches\n     @@ Documentation/git-am.txt: OPTIONS\n       \n      +--empty=(die|drop|keep)::\n      +\tBy default, or when the option is set to 'die', the command\n     -+\terrors out on an input e-mail message that lacks a patch. When\n     -+\tthis option is set to 'drop', skip such an e-mail message instead.\n     ++\terrors out on an input e-mail message lacking a patch\n     ++\tand stops into the middle of the current am session. When this\n     ++\toption is set to 'drop', skip such an e-mail message instead.\n      +\tWhen this option is set to 'keep', create an empty commit,\n      +\trecording the contents of the e-mail message as its log.\n      +\n 3:  08bd397ae7a ! 3:  cbd822d4340 am: support --allow-empty to record specific empty patches\n     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n       \tDefaults to `raw`.\n       \n      +--allow-empty::\n     -+\tRecord the empty patch as an empty commit with\n     ++\tAfter a patch failure on an input e-mail message lacking a patch,\n     ++\tthe user can still record the empty patch as an empty commit with\n      +\tthe contents of the e-mail message as its log.\n      +\n       DISCUSSION\n\n-- \ngitgitgadget\n"},{"id":"442737","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638329848.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v14.git.1638329848.gitgitgadget@gmail.com","subject":"[PATCH v14 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-01T03:37:26Z","receivedAt":"2021-12-01T03:37:51Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"442738","messageId":"b6a04fc12dfd1680b81bc946b09d4519edf3555e.1638329848.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v14.git.1638329848.gitgitgadget@gmail.com","subject":"[PATCH v14 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-01T03:37:27Z","receivedAt":"2021-12-01T03:37:56Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 108 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..cf9cace9678 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, or when the option is set to 'die', the command\n+\terrors out on an input e-mail message lacking a patch\n+\tand stops into the middle of the current am session. When this\n+\toption is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..cc6512275aa 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..8c8bd4db220 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"442739","messageId":"cbd822d4340f8f3d832e9d6209ee8db6e538fca1.1638329848.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v14.git.1638329848.gitgitgadget@gmail.com","subject":"[PATCH v14 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-01T03:37:28Z","receivedAt":"2021-12-01T03:38:03Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 ++++++-\n builtin/am.c             | 29 ++++++++++++++++++++---------\n t/t4150-am.sh            | 24 ++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 54 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex cf9cace9678..b6c065b3f7a 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tthe user can still record the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex cc6512275aa..58c7638b9bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tto_keep = 1;\n \t\t\t\tbreak;\n \t\t\tcase DIE_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1898,19 +1899,24 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when there is no changes.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n-\t\tdie_user_resolve(state);\n+\t\tif (allow_empty)\n+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n+\t\telse {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tif (unmerged_cache()) {\n@@ -2237,7 +2243,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2390,6 +2397,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2498,7 +2508,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 8c8bd4db220..3e60460022f 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1201,4 +1201,28 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..9309becfe03 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d578a0e9192 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"442841","messageId":"xmqqy253x1pe.fsf@gitster.g","threadId":"56877","inReplyTo":"cbd822d4340f8f3d832e9d6209ee8db6e538fca1.1638329848.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v14 3/3] am: support --allow-empty to record specific empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-02T00:58:05Z","receivedAt":"2021-12-02T00:58:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"\"徐沛文 (Aleen)\" via GitGitGadget\"  <gitgitgadget@gmail.com>\nwrites:\n\n> +test_expect_success 'skip an empty patch in the middle of an am session' '\n> +\tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am empty-commit.patch >err &&\n> +\tgrep \"Patch is empty.\" err &&\n> +\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n> +\tgit am --skip &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit rev-parse empty-commit^ >expected &&\n> +\tgit rev-parse HEAD >actual &&\n> +\ttest_cmp expected actual\n> +'\n> +\n> +test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n> +\tgit checkout empty-commit^ &&\n> +\ttest_must_fail git am empty-commit.patch >err &&\n> +\tgrep \"Patch is empty.\" err &&\n> +\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n> +\tgit am --allow-empty &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit show empty-commit --format=\"%s\" >expected &&\n> +\tgit show HEAD --format=\"%s\" >actual &&\n> +\ttest_cmp actual expected\n> +'\n\nThese two are \"positive\" tests, i.e. the feature does the right\nthing when used in the expected situation.\n\nI am worried about the cases where \"--allow-empty\" is used and\ncreates a commit that is empty, when it is reasonably expected to\neither create a non-empty commit or still fail the same way to give\nthe user another chance to try.\n\nFor example, when \"git am -3\" fails on such an email message (there\nare two failure modes: (a) one results in an unmerged index due to\nmerge conflicts, (b) the other results in a clean index due to not\nfinding the blob object recorded as a preimage), what happens when\nthe user runs \"git am --allow-empty\", after:\n\n (1) doing nothing since the failed \"git am -3\" application,\n (2) resolving the conflicts in working tree, or\n (3) resolving the conflicts in working tree and recording the\n     resolution in the index.\n\nThere are 6 cases in the above matrix, and \"git am --allow-empty\"\nshould not create an empty commit in any one of them.  In 1a and 1b,\n\"git am --allow-empty\" should continue to fail (\"git am --skip\"\nwould be a way to ignore and proceed).  3a and 3b should record a\nnon-empty commit as the result.\n\nSimilarly, when an email message with a patch is given to \"git am\"\nand the application fails, and the user runs \"git am --allow-empty\"\nwithout doing anything in the working tree, what happens?  It should\nnot happily create an empty commit with only the log message,\nwithout the change, but continue to fail.  If the user resolves and\nregisters the resolution to the index before \"git am --allow-empty\",\nthen the command should create a non-empty commit.\n\nHaving positive tests are of course important to avoid regression in\nthe future, but negative tests, the new and shiny feature does not\nmisbehave when it shouldn't even kick in, is even more important.\n\nThanks.\n"},{"id":"443045","messageId":"nycvar.QRO.7.76.6.2112032327180.63@tvgsbejvaqbjf.bet","threadId":"56877","inReplyTo":"b6a04fc12dfd1680b81bc946b09d4519edf3555e.1638329848.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v14 2/3] am: support --empty=<option> to handle empty patches","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-12-03T22:30:45Z","receivedAt":"2021-12-03T22:33:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Aleen,\n\nOn Wed, 1 Dec 2021, 徐沛文 (Aleen) via GitGitGadget wrote:\n\n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index 0a4a984dfde..cf9cace9678 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -16,6 +16,7 @@ SYNOPSIS\n>  \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n>  \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n>  \t [--quoted-cr=<action>]\n> +\t [--empty=(die|drop|keep)]\n\nI have to agree with Elijah that \"to die\" is used differently in Git's\ncontext. This should probably be called \"stop\" instead. Or \"error\". But\nnot \"die\".\n\nCiao,\nDscho\n\nP.S.: The enum value should probably have an `_ON_` in it, i.e.\n`STOP_ON_EMPTY_COMMIT` or `ERROR_ON_EMPTY_COMMIT`.\n"},{"id":"443128","messageId":"7f165c0d.1f.17d8d61d9b1.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqy253x1pe.fsf@gitster.g","subject":"Re: [PATCH v14 3/3] am: support --allow-empty to record specific empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-12-06T01:35:39Z","receivedAt":"2021-12-06T01:35:47Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"Dears Hamano,\n\n    For those 6 cases, it seems `git-am` should only work when the am session\n    was stopped in the middle according to an empty patch, but not when the index\n    has not changed. Does that mean we should record a new state for the am state?\n    If that's right, I will try to patch it via version 15, and try to add additional\n    test cases for these 6 situations. I was a little busy those days and sorry for\n    the late answer.\n\nAleen\n\n> These two are \"positive\" tests, i.e. the feature does the right\n> thing when used in the expected situation.\n> \n> I am worried about the cases where \"--allow-empty\" is used and\n> creates a commit that is empty, when it is reasonably expected to\n> either create a non-empty commit or still fail the same way to give\n> the user another chance to try.\n> \n> For example, when \"git am -3\" fails on such an email message (there\n> are two failure modes: (a) one results in an unmerged index due to\n> merge conflicts, (b) the other results in a clean index due to not\n> finding the blob object recorded as a preimage), what happens when\n> the user runs \"git am --allow-empty\", after:\n> \n>  (1) doing nothing since the failed \"git am -3\" application,\n>  (2) resolving the conflicts in working tree, or\n>  (3) resolving the conflicts in working tree and recording the\n>      resolution in the index.\n> \n> There are 6 cases in the above matrix, and \"git am --allow-empty\"\n> should not create an empty commit in any one of them.  In 1a and 1b,\n> \"git am --allow-empty\" should continue to fail (\"git am --skip\"\n> would be a way to ignore and proceed).  3a and 3b should record a\n> non-empty commit as the result.\n> \n> Similarly, when an email message with a patch is given to \"git am\"\n> and the application fails, and the user runs \"git am --allow-empty\"\n> without doing anything in the working tree, what happens?  It should\n> not happily create an empty commit with only the log message,\n> without the change, but continue to fail.  If the user resolves and\n> registers the resolution to the index before \"git am --allow-empty\",\n> then the command should create a non-empty commit.\n> \n> Having positive tests are of course important to avoid regression in\n> the future, but negative tests, the new and shiny feature does not\n> misbehave when it shouldn't even kick in, is even more important.\n> \n> Thanks.\n"},{"id":"443144","messageId":"pull.1076.v15.git.1638783690.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v14.git.1638329848.gitgitgadget@gmail.com","subject":"[PATCH v15 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-06T09:41:27Z","receivedAt":"2021-12-06T09:41:46Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n----------------------------------------------------------------------------\n\nChanges since v14:\n\n 1. reimplement the 'die' value and stop the whole session. (Expected a\n    reroll)\n 2. the --allow-empty option is a valid resume value only when: (Expected a\n    reroll)\n    * index has not changed\n    * lacking a patch\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           |  16 ++++-\n Documentation/git-format-patch.txt |   6 +-\n builtin/am.c                       | 100 ++++++++++++++++++++++++----\n t/t4150-am.sh                      | 103 +++++++++++++++++++++++++++++\n t/t7512-status-help.sh             |   1 +\n wt-status.c                        |   3 +\n 6 files changed, 214 insertions(+), 15 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v15\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v15\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v14:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  b6a04fc12df ! 2:  8ec8e212672 am: support --empty=<option> to handle empty patches\n     @@ Documentation/git-am.txt: OPTIONS\n       \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n      +--empty=(die|drop|keep)::\n     -+\tBy default, or when the option is set to 'die', the command\n     -+\terrors out on an input e-mail message lacking a patch\n     -+\tand stops into the middle of the current am session. When this\n     -+\toption is set to 'drop', skip such an e-mail message instead.\n     ++\tBy default, the command errors out on an input e-mail message\n     ++\tlacking a patch and stops into the middle of the current am session.\n     ++\tWhen this option is set to 'die', the whole session dies with error.\n     ++\tWhen this option is set to 'drop', skip such an e-mail message instead.\n      +\tWhen this option is set to 'keep', create an empty commit,\n      +\trecording the contents of the e-mail message as its log.\n      +\n     @@ builtin/am.c: enum show_patch_type {\n       };\n       \n      +enum empty_action {\n     -+\tDIE_EMPTY_COMMIT = 0,  /* output errors */\n     ++\tERR_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n     ++\tDIE_EMPTY_COMMIT,      /* output errors and stop the whole am session */\n      +\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n      +\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n      +};\n     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n      +\t\t\t\tto_keep = 1;\n      +\t\t\t\tbreak;\n      +\t\t\tcase DIE_EMPTY_COMMIT:\n     ++\t\t\t\tam_destroy(state);\n     ++\t\t\t\tdie(_(\"Patch is empty.\"));\n     ++\t\t\t\tbreak;\n     ++\t\t\tcase ERR_EMPTY_COMMIT:\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tdie_user_resolve(state);\n      +\t\t\t\tbreak;\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n       \t\t  N_(\"GPG-sign commits\"),\n       \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n     -+\t\tOPT_CALLBACK_F(0, \"empty\", &state.empty_type, \"{drop,keep,die}\",\n     ++\t\tOPT_CALLBACK_F(ERR_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{die,drop,keep}\",\n      +\t\t  N_(\"how to handle empty patches\"),\n      +\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n       \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +\ttest_cmp expected err\n      +'\n      +\n     -+test_expect_success 'a message without a patch is an error (default)' '\n     ++test_expect_success 'a message without a patch is an error and stop in the middle of an am session (default)' '\n      +\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n     ++\ttest_path_is_dir .git/rebase-apply &&\n      +\tgrep \"Patch is empty\" err\n      +'\n      +\n     -+test_expect_success 'a message without a patch is an error where an explicit \"--empty=die\" is given' '\n     -+\ttest_when_finished \"git am --abort || :\" &&\n     -+\ttest_must_fail git am --empty=die empty-commit.patch >err &&\n     -+\tgrep \"Patch is empty.\" err\n     ++test_expect_success 'a message without a patch is an error and exit where an explicit \"--empty=die\" is given' '\n     ++\ttest_must_fail git am --empty=die empty-commit.patch 2>err &&\n     ++\ttest_path_is_missing .git/rebase-apply &&\n     ++\tgrep \"fatal: Patch is empty.\" err\n      +'\n      +\n      +test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n 3:  cbd822d4340 ! 3:  d669406a312 am: support --allow-empty to record specific empty patches\n     @@ Commit message\n          am: support --allow-empty to record specific empty patches\n      \n          This option helps to record specific empty patches in the middle\n     -    of an am session.\n     +    of an am session. However, it is a valid resume value only when:\n     +\n     +        1. index has not changed\n     +        2. lacking a branch\n      \n          Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n      \n       ## builtin/am.c ##\n      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n     - \t\t\t\tto_keep = 1;\n     + \t\t\t\tdie(_(\"Patch is empty.\"));\n       \t\t\t\tbreak;\n     - \t\t\tcase DIE_EMPTY_COMMIT:\n     + \t\t\tcase ERR_EMPTY_COMMIT:\n      -\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n      +\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n     @@ builtin/am.c: next:\n      -static void am_resolve(struct am_state *state)\n      +static void am_resolve(struct am_state *state, int allow_empty)\n       {\n     ++\tint index_changed;\n     ++\n       \tvalidate_resume_state(state);\n       \n       \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n       \n     - \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n     +-\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n      -\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n      -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n      -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n     --\t\tdie_user_resolve(state);\n     ++\t/**\n     ++\t * \"--allow-empty\" is a valid resume value only when:\n     ++\t *   1. index has not changed\n     ++\t *   2. lacking a patch\n     ++\t */\n     ++\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n     ++\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n     ++\t\tprintf_ln(_(\"Invalid resume value.\"));\n     + \t\tdie_user_resolve(state);\n     + \t}\n     + \n     ++\tif (!index_changed) {\n      +\t\tif (allow_empty)\n      +\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n      +\t\telse {\n     @@ builtin/am.c: next:\n      +\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n      +\t\t\tdie_user_resolve(state);\n      +\t\t}\n     - \t}\n     - \n     ++\t}\n     ++\n       \tif (unmerged_cache()) {\n     + \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n     + \t\t\t\"You should 'git add' each file with resolved conflicts to mark them as such.\\n\"\n      @@ builtin/am.c: enum resume_type {\n       \tRESUME_SKIP,\n       \tRESUME_ABORT,\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\tgit show HEAD --format=\"%s\" >actual &&\n      +\ttest_cmp actual expected\n      +'\n     ++\n     ++test_expect_success 'cannot create empty commits when the index is changed' '\n     ++\tgit checkout empty-commit^ &&\n     ++\ttest_must_fail git am empty-commit.patch >err &&\n     ++\t: >empty-file &&\n     ++\tgit add empty-file &&\n     ++\ttest_must_fail git am --allow-empty >err &&\n     ++\tgrep \"Invalid resume value.\" err\n     ++'\n     ++\n     ++test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n     ++\tgit rev-parse HEAD >expected &&\n     ++\ttest_must_fail git am seq.patch &&\n     ++\ttest_must_fail git am --allow-empty >err &&\n     ++\tgrep \"Invalid resume value.\" err &&\n     ++\tgit rev-parse HEAD >actual &&\n     ++\ttest_cmp actual expected\n     ++'\n     ++\n     ++test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n     ++\tgit rev-parse HEAD >expected &&\n     ++\ttest_must_fail git am -3 seq.patch &&\n     ++\ttest_must_fail git am --allow-empty >err &&\n     ++\tgrep \"Invalid resume value.\" err &&\n     ++\tgit rev-parse HEAD >actual &&\n     ++\ttest_cmp actual expected\n     ++'\n      +\n       test_done\n      \n\n-- \ngitgitgadget\n"},{"id":"443145","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638783690.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v15.git.1638783690.gitgitgadget@gmail.com","subject":"[PATCH v15 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-06T09:41:28Z","receivedAt":"2021-12-06T09:41:47Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"443146","messageId":"8ec8e212672898b9adec2dcc63d7997f5446cfff.1638783690.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v15.git.1638783690.gitgitgadget@gmail.com","subject":"[PATCH v15 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-06T09:41:29Z","receivedAt":"2021-12-06T09:41:49Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('die', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 ++++++\n builtin/am.c             | 60 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++\n 3 files changed, 114 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..4c31c39bf81 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(die|drop|keep)::\n+\tBy default, the command errors out on an input e-mail message\n+\tlacking a patch and stops into the middle of the current am session.\n+\tWhen this option is set to 'die', the whole session dies with error.\n+\tWhen this option is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..43a3b1cf038 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,13 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tERR_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n+\tDIE_EMPTY_COMMIT,      /* output errors and stop the whole am session */\n+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +125,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +186,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"die\"))\n+\t\t*opt_value = DIE_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1275,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1785,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1815,31 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase DIE_EMPTY_COMMIT:\n+\t\t\t\tam_destroy(state);\n+\t\t\t\tdie(_(\"Patch is empty.\"));\n+\t\t\t\tbreak;\n+\t\t\tcase ERR_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1873,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2404,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(ERR_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{die,drop,keep}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..f2b765644e5 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,48 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error and stop in the middle of an am session (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\ttest_path_is_dir .git/rebase-apply &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error and exit where an explicit \"--empty=die\" is given' '\n+\ttest_must_fail git am --empty=die empty-commit.patch 2>err &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgrep \"fatal: Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443147","messageId":"d669406a312276e3601f1cc876006fddda051d8d.1638783690.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v15.git.1638783690.gitgitgadget@gmail.com","subject":"[PATCH v15 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-06T09:41:30Z","receivedAt":"2021-12-06T09:41:50Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session. However, it is a valid resume value only when:\n\n    1. index has not changed\n    2. lacking a branch\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 +++++-\n builtin/am.c             | 42 ++++++++++++++++++++++++-------\n t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 96 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 4c31c39bf81..a5223143222 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(die|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tthe user can still record the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex 43a3b1cf038..974924cb83d 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1830,7 +1830,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tdie(_(\"Patch is empty.\"));\n \t\t\t\tbreak;\n \t\t\tcase ERR_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1903,21 +1904,39 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when there is no changes.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n+\tint index_changed;\n+\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n-\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n+\t/**\n+\t * \"--allow-empty\" is a valid resume value only when:\n+\t *   1. index has not changed\n+\t *   2. lacking a patch\n+\t */\n+\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n+\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n+\t\tprintf_ln(_(\"Invalid resume value.\"));\n \t\tdie_user_resolve(state);\n \t}\n \n+\tif (!index_changed) {\n+\t\tif (allow_empty)\n+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n+\t\telse {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n+\t}\n+\n \tif (unmerged_cache()) {\n \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n \t\t\t\"You should 'git add' each file with resolved conflicts to mark them as such.\\n\"\n@@ -2242,7 +2261,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2395,6 +2415,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2503,7 +2526,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex f2b765644e5..3f81aa3d6a1 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1202,4 +1202,57 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when the index is changed' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\t: >empty-file &&\n+\tgit add empty-file &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err\n+'\n+\n+test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am -3 seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..9309becfe03 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d578a0e9192 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"443230","messageId":"pull.1076.v16.git.1638853295.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v15.git.1638783690.gitgitgadget@gmail.com","subject":"[PATCH v16 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T05:01:32Z","receivedAt":"2021-12-07T05:01:41Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n----------------------------------------------------------------------------\n\nChanges since v14:\n\n 1. reimplement the 'die' value and stop the whole session. (Expected a\n    reroll)\n 2. the --allow-empty option is a valid resume value only when: (Expected a\n    reroll)\n    * index has not changed\n    * lacking a patch\n\n----------------------------------------------------------------------------\n\nChanges since v15:\n\n 1. rename \"die\" to \"stop\".\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           |  16 ++++-\n Documentation/git-format-patch.txt |   6 +-\n builtin/am.c                       |  90 +++++++++++++++++++++----\n t/t4150-am.sh                      | 102 +++++++++++++++++++++++++++++\n t/t7512-status-help.sh             |   1 +\n wt-status.c                        |   3 +\n 6 files changed, 203 insertions(+), 15 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v16\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v16\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v15:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  8ec8e212672 ! 2:  b9e03f2342b am: support --empty=<option> to handle empty patches\n     @@ Commit message\n          commits that emit no changes, the 'git-am' command should also\n          support an option, named as '--empty', to specify how to handle\n          those empty patches. In this commit, we have implemented three\n     -    valid options ('die', 'drop' and 'keep').\n     +    valid options ('stop', 'drop' and 'keep').\n      \n          Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n     @@ Documentation/git-am.txt: SYNOPSIS\n       \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n       \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n       \t [--quoted-cr=<action>]\n     -+\t [--empty=(die|drop|keep)]\n     ++\t [--empty=(stop|drop|keep)]\n       \t [(<mbox> | <Maildir>)...]\n       'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n       \n     @@ Documentation/git-am.txt: OPTIONS\n       --quoted-cr=<action>::\n       \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n       \n     -+--empty=(die|drop|keep)::\n     -+\tBy default, the command errors out on an input e-mail message\n     -+\tlacking a patch and stops into the middle of the current am session.\n     -+\tWhen this option is set to 'die', the whole session dies with error.\n     -+\tWhen this option is set to 'drop', skip such an e-mail message instead.\n     ++--empty=(stop|drop|keep)::\n     ++\tBy default, or when the option is set to 'stop', the command\n     ++\terrors out on an input e-mail message lacking a patch\n     ++\tand stops into the middle of the current am session. When this\n     ++\toption is set to 'drop', skip such an e-mail message instead.\n      +\tWhen this option is set to 'keep', create an empty commit,\n      +\trecording the contents of the e-mail message as its log.\n      +\n     @@ builtin/am.c: enum show_patch_type {\n       };\n       \n      +enum empty_action {\n     -+\tERR_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n     -+\tDIE_EMPTY_COMMIT,      /* output errors and stop the whole am session */\n     -+\tDROP_EMPTY_COMMIT,     /* skip with a notice message, unless \"--quiet\" has been passed */\n     -+\tKEEP_EMPTY_COMMIT      /* keep recording as empty commits */\n     ++\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n     ++\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n     ++\tKEEP_EMPTY_COMMIT          /* keep recording as empty commits */\n      +};\n      +\n       struct am_state {\n     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,\n      +\n      +\tBUG_ON_OPT_NEG(unset);\n      +\n     -+\tif (!strcmp(arg, \"die\"))\n     -+\t\t*opt_value = DIE_EMPTY_COMMIT;\n     ++\tif (!strcmp(arg, \"stop\"))\n     ++\t\t*opt_value = STOP_ON_EMPTY_COMMIT;\n      +\telse if (!strcmp(arg, \"drop\"))\n      +\t\t*opt_value = DROP_EMPTY_COMMIT;\n      +\telse if (!strcmp(arg, \"keep\"))\n     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n      +\t\t\tcase KEEP_EMPTY_COMMIT:\n      +\t\t\t\tto_keep = 1;\n      +\t\t\t\tbreak;\n     -+\t\t\tcase DIE_EMPTY_COMMIT:\n     -+\t\t\t\tam_destroy(state);\n     -+\t\t\t\tdie(_(\"Patch is empty.\"));\n     -+\t\t\t\tbreak;\n     -+\t\t\tcase ERR_EMPTY_COMMIT:\n     ++\t\t\tcase STOP_ON_EMPTY_COMMIT:\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tdie_user_resolve(state);\n      +\t\t\t\tbreak;\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n       \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n       \t\t  N_(\"GPG-sign commits\"),\n       \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n     -+\t\tOPT_CALLBACK_F(ERR_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{die,drop,keep}\",\n     ++\t\tOPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{stop,drop,keep}\",\n      +\t\t  N_(\"how to handle empty patches\"),\n      +\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n       \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +\ttest_cmp expected err\n      +'\n      +\n     -+test_expect_success 'a message without a patch is an error and stop in the middle of an am session (default)' '\n     ++test_expect_success 'a message without a patch is an error (default)' '\n      +\ttest_when_finished \"git am --abort || :\" &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n     -+\ttest_path_is_dir .git/rebase-apply &&\n      +\tgrep \"Patch is empty\" err\n      +'\n      +\n     -+test_expect_success 'a message without a patch is an error and exit where an explicit \"--empty=die\" is given' '\n     -+\ttest_must_fail git am --empty=die empty-commit.patch 2>err &&\n     -+\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgrep \"fatal: Patch is empty.\" err\n     ++test_expect_success 'a message without a patch is an error where an explicit \"--empty=stop\" is given' '\n     ++\ttest_when_finished \"git am --abort || :\" &&\n     ++\ttest_must_fail git am --empty=stop empty-commit.patch >err &&\n     ++\tgrep \"Patch is empty.\" err\n      +'\n      +\n      +test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n 3:  d669406a312 ! 3:  abcdfa1b375 am: support --allow-empty to record specific empty patches\n     @@ Commit message\n       ## Documentation/git-am.txt ##\n      @@ Documentation/git-am.txt: SYNOPSIS\n       \t [--quoted-cr=<action>]\n     - \t [--empty=(die|drop|keep)]\n     + \t [--empty=(stop|drop|keep)]\n       \t [(<mbox> | <Maildir>)...]\n      -'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n      +'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n      \n       ## builtin/am.c ##\n      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n     - \t\t\t\tdie(_(\"Patch is empty.\"));\n     + \t\t\t\tto_keep = 1;\n       \t\t\t\tbreak;\n     - \t\t\tcase ERR_EMPTY_COMMIT:\n     + \t\t\tcase STOP_ON_EMPTY_COMMIT:\n      -\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n      +\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n     @@ builtin/am.c: next:\n        * all the hard work, and we do not have to do any patch application. Just\n      - * trust and commit what the user has in the index and working tree.\n      + * trust and commit what the user has in the index and working tree. If `allow_empty`\n     -+ * is true, commit as an empty commit when there is no changes.\n     ++ * is true, commit as an empty commit when index had not changed and lacking a patch.\n        */\n      -static void am_resolve(struct am_state *state)\n      +static void am_resolve(struct am_state *state, int allow_empty)\n     @@ builtin/am.c: next:\n      -\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n      -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n      -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n     -+\t/**\n     -+\t * \"--allow-empty\" is a valid resume value only when:\n     -+\t *   1. index has not changed\n     -+\t *   2. lacking a patch\n     -+\t */\n      +\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n      +\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n      +\t\tprintf_ln(_(\"Invalid resume value.\"));\n\n-- \ngitgitgadget\n"},{"id":"443231","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638853296.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v16.git.1638853295.gitgitgadget@gmail.com","subject":"[PATCH v16 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T05:01:33Z","receivedAt":"2021-12-07T05:01:44Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"443232","messageId":"b9e03f2342b3fc52515f063f28b34ad9e1dc338a.1638853296.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v16.git.1638853295.gitgitgadget@gmail.com","subject":"[PATCH v16 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T05:01:34Z","receivedAt":"2021-12-07T05:01:44Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('stop', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 108 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..7676bd58ae7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(stop|drop|keep)::\n+\tBy default, or when the option is set to 'stop', the command\n+\terrors out on an input e-mail message lacking a patch\n+\tand stops into the middle of the current am session. When this\n+\toption is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..f45aa33366f 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n+\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT          /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"stop\"))\n+\t\t*opt_value = STOP_ON_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase STOP_ON_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{stop,drop,keep}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..ce8e8e79ace 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=stop\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=stop empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443233","messageId":"abcdfa1b3755f92bacff3301524ea56913460a58.1638853296.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v16.git.1638853295.gitgitgadget@gmail.com","subject":"[PATCH v16 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T05:01:35Z","receivedAt":"2021-12-07T05:01:48Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session. However, it is a valid resume value only when:\n\n    1. index has not changed\n    2. lacking a branch\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 +++++-\n builtin/am.c             | 37 +++++++++++++++++++++-------\n t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 91 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 7676bd58ae7..8d3aa552a20 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tthe user can still record the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex f45aa33366f..05cbb42dd12 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tto_keep = 1;\n \t\t\t\tbreak;\n \t\t\tcase STOP_ON_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1898,21 +1899,34 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when index had not changed and lacking a patch.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n+\tint index_changed;\n+\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n-\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n+\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n+\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n+\t\tprintf_ln(_(\"Invalid resume value.\"));\n \t\tdie_user_resolve(state);\n \t}\n \n+\tif (!index_changed) {\n+\t\tif (allow_empty)\n+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n+\t\telse {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n+\t}\n+\n \tif (unmerged_cache()) {\n \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n \t\t\t\"You should 'git add' each file with resolved conflicts to mark them as such.\\n\"\n@@ -2237,7 +2251,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2390,6 +2405,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2498,7 +2516,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex ce8e8e79ace..e154b4201b7 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1201,4 +1201,57 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when the index is changed' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\t: >empty-file &&\n+\tgit add empty-file &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err\n+'\n+\n+test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am -3 seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..9309becfe03 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d578a0e9192 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"443244","messageId":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v16.git.1638853295.gitgitgadget@gmail.com","subject":"[PATCH v17 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T08:31:50Z","receivedAt":"2021-12-07T08:31:59Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n----------------------------------------------------------------------------\n\nChanges since v14:\n\n 1. reimplement the 'die' value and stop the whole session. (Expected a\n    reroll)\n 2. the --allow-empty option is a valid resume value only when: (Expected a\n    reroll)\n    * index has not changed\n    * lacking a patch\n\n----------------------------------------------------------------------------\n\nChanges since v15:\n\n 1. rename \"die\" to \"stop\".\n\n----------------------------------------------------------------------------\n\nChanges since v16:\n\n 1. fix typo from \"had\" to \"has\" in the comment.\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           |  16 ++++-\n Documentation/git-format-patch.txt |   6 +-\n builtin/am.c                       |  90 +++++++++++++++++++++----\n t/t4150-am.sh                      | 102 +++++++++++++++++++++++++++++\n t/t7512-status-help.sh             |   1 +\n wt-status.c                        |   3 +\n 6 files changed, 203 insertions(+), 15 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v17\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v17\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v16:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  b9e03f2342b = 2:  b9e03f2342b am: support --empty=<option> to handle empty patches\n 3:  abcdfa1b375 ! 3:  ea2dc088b37 am: support --allow-empty to record specific empty patches\n     @@ builtin/am.c: next:\n        * all the hard work, and we do not have to do any patch application. Just\n      - * trust and commit what the user has in the index and working tree.\n      + * trust and commit what the user has in the index and working tree. If `allow_empty`\n     -+ * is true, commit as an empty commit when index had not changed and lacking a patch.\n     ++ * is true, commit as an empty commit when index has not changed and lacking a patch.\n        */\n      -static void am_resolve(struct am_state *state)\n      +static void am_resolve(struct am_state *state, int allow_empty)\n\n-- \ngitgitgadget\n"},{"id":"443245","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638865913.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","subject":"[PATCH v17 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T08:31:51Z","receivedAt":"2021-12-07T08:32:00Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"443246","messageId":"b9e03f2342b3fc52515f063f28b34ad9e1dc338a.1638865913.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","subject":"[PATCH v17 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T08:31:52Z","receivedAt":"2021-12-07T08:32:00Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('stop', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++\n 3 files changed, 108 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..7676bd58ae7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(stop|drop|keep)::\n+\tBy default, or when the option is set to 'stop', the command\n+\terrors out on an input e-mail message lacking a patch\n+\tand stops into the middle of the current am session. When this\n+\toption is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..f45aa33366f 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n+\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT          /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"stop\"))\n+\t\t*opt_value = STOP_ON_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tbreak;\n+\t\t\tcase STOP_ON_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{stop,drop,keep}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..ce8e8e79ace 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=stop\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=stop empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443247","messageId":"ea2dc088b37670cd7969a57777798b8f5bdd94e4.1638865913.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","subject":"[PATCH v17 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-07T08:31:53Z","receivedAt":"2021-12-07T08:32:07Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session. However, it is a valid resume value only when:\n\n    1. index has not changed\n    2. lacking a branch\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 +++++-\n builtin/am.c             | 37 +++++++++++++++++++++-------\n t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  3 +++\n 5 files changed, 91 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 7676bd58ae7..8d3aa552a20 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tthe user can still record the empty patch as an empty commit with\n+\tthe contents of the e-mail message as its log.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex f45aa33366f..89a29ccac89 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n \t\t\t\tto_keep = 1;\n \t\t\t\tbreak;\n \t\t\tcase STOP_ON_EMPTY_COMMIT:\n-\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n \t\t\t\tdie_user_resolve(state);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -1898,21 +1899,34 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when index has not changed and lacking a patch.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n+\tint index_changed;\n+\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n-\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n+\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n+\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n+\t\tprintf_ln(_(\"Invalid resume value.\"));\n \t\tdie_user_resolve(state);\n \t}\n \n+\tif (!index_changed) {\n+\t\tif (allow_empty)\n+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n+\t\telse {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n+\t}\n+\n \tif (unmerged_cache()) {\n \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n \t\t\t\"You should 'git add' each file with resolved conflicts to mark them as such.\\n\"\n@@ -2237,7 +2251,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY\n };\n \n struct resume_mode {\n@@ -2390,6 +2405,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2498,7 +2516,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex ce8e8e79ace..e154b4201b7 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1201,4 +1201,57 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \ttest_cmp actual expected\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%s\" >expected &&\n+\tgit show HEAD --format=\"%s\" >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when the index is changed' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\t: >empty-file &&\n+\tgit add empty-file &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err\n+'\n+\n+test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am -3 seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\tgrep \"Invalid resume value.\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..9309becfe03 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --skip\" to skip this patch)\n   (use \"git am --abort\" to restore the original branch)\n \ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..d578a0e9192 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n \t\tif (!s->state.am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n+\t\telse\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n \t\tstatus_printf_ln(s, color,\n-- \ngitgitgadget\n"},{"id":"443319","messageId":"xmqq4k7k9td6.fsf@gitster.g","threadId":"56877","inReplyTo":"b9e03f2342b3fc52515f063f28b34ad9e1dc338a.1638865913.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v17 2/3] am: support --empty=<option> to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T18:12:37Z","receivedAt":"2021-12-07T18:12:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"\"徐沛文 (Aleen)\" via GitGitGadget\"  <gitgitgadget@gmail.com>\nwrites:\n\n\nThis step look mostly good and well done, except for just a few\nthings that remain.\n\n> +enum empty_action {\n> +\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n> +\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n> +\tKEEP_EMPTY_COMMIT          /* keep recording as empty commits */\n> +};\n\nIt is friendly to future developers to end the last item in enum\nwith a comma, unless the current last item MUST stay to be the last\none even when they add new ones.  I.e.\n\n\tKEEP_EMPTY_COMMIT,          /* keep recording as empty commits */\n\n>  struct am_state {\n>  \t/* state directory path */\n>  \tchar *dir;\n> @@ -118,6 +124,7 @@ struct am_state {\n>  \tint message_id;\n>  \tint scissors; /* enum scissors_type */\n>  \tint quoted_cr; /* enum quoted_cr_action */\n> +\tint empty_type; /* enum empty_action */\n\nMental note.  After this series graduates to 'master', at some point\nin the future, we should clean these members up to be of their\nrespective enum types, not \"int\".\n\n> @@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n>  \twhile (state->cur <= state->last) {\n>  \t\tconst char *mail = am_path(state, msgnum(state));\n>  \t\tint apply_status;\n> +\t\tint to_keep;\n>  \n>  \t\treset_ident_date();\n>  \n> @@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)\n>  \t\tif (state->interactive && do_interactive(state))\n>  \t\t\tgoto next;\n>  \n> +\t\tto_keep = 0;\n> +\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n> +\t\t\tswitch (state->empty_type) {\n> +\t\t\tcase DROP_EMPTY_COMMIT:\n> +\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n> +\t\t\t\tgoto next;\n> +\t\t\t\tbreak;\n> +\t\t\tcase KEEP_EMPTY_COMMIT:\n> +\t\t\t\tto_keep = 1;\n\nThis causes the code that produces the \"Applying\" message jumped\nover, so the user will not see anything done for this step.  \n\nI think we want to mimic the above case arm and do something like\n\n\t\t\t\tsay(state, stdout, _(\"Creating an empty commit: %.*s\"),\n\t\t\t\t\tlinelen(state->msg), state->msg);\n\nto avoid being mum about what was done in this step.\n\n> +\t\t\t\tbreak;\n> +\t\t\tcase STOP_ON_EMPTY_COMMIT:\n> +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\t\tdie_user_resolve(state);\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t}\n> +\n>  \t\tif (run_applypatch_msg_hook(state))\n>  \t\t\texit(1);\n> +\t\tif (to_keep)\n> +\t\t\tgoto commit;\n>  \n>  \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n>  \n> @@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)\n>  \t\t\tdie_user_resolve(state);\n>  \t\t}\n>  \n> +commit:\n>  \t\tdo_commit(state);\n>  \n>  next:\n\n> +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n> +\tgit am --empty=keep empty-commit.patch &&\n> +\ttest_path_is_missing .git/rebase-apply &&\n> +\tgit show empty-commit --format=\"%s\" >expected &&\n> +\tgit show HEAD --format=\"%s\" >actual &&\n\nFor the test data prepared by the earlier part of this patch, this\ndoes not make a difference, but by using %B instead of %s, I think\nyou can catch a future bug that only keeps the subject intact while\nmunging the body of the message.\n\nOther than these, looking quite good.\n\nThanks.\n"},{"id":"443323","messageId":"xmqqpmq88eai.fsf@gitster.g","threadId":"56877","inReplyTo":"ea2dc088b37670cd7969a57777798b8f5bdd94e4.1638865913.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v17 3/3] am: support --allow-empty to record specific empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T18:23:33Z","receivedAt":"2021-12-07T18:23:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"\"徐沛文 (Aleen)\" via GitGitGadget\"  <gitgitgadget@gmail.com>\nwrites:\n\n> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n>  <aleen42@vip.qq.com>\n>\n> This option helps to record specific empty patches in the middle\n> of an am session. However, it is a valid resume value only when:\n>\n>     1. index has not changed\n>     2. lacking a branch\n>\n> Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n> ---\n>  Documentation/git-am.txt |  7 +++++-\n>  builtin/am.c             | 37 +++++++++++++++++++++-------\n>  t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++\n>  t/t7512-status-help.sh   |  1 +\n>  wt-status.c              |  3 +++\n>  5 files changed, 91 insertions(+), 10 deletions(-)\n>\n> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\n> index 7676bd58ae7..8d3aa552a20 100644\n> --- a/Documentation/git-am.txt\n> +++ b/Documentation/git-am.txt\n> @@ -18,7 +18,7 @@ SYNOPSIS\n>  \t [--quoted-cr=<action>]\n>  \t [--empty=(stop|drop|keep)]\n>  \t [(<mbox> | <Maildir>)...]\n> -'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n> +'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n>  \tthe e-mail message; if `diff`, show the diff portion only.\n>  \tDefaults to `raw`.\n>  \n> +--allow-empty::\n> +\tAfter a patch failure on an input e-mail message lacking a patch,\n> +\tthe user can still record the empty patch as an empty commit with\n> +\tthe contents of the e-mail message as its log.\n\n\"The user can still do X\" is not technically incorrect, but this is\nnot merely a possiblity but does actively does X, so\n\n\tAfter a patch failure on an input e-mail message lacking a patch,\n\tcreate an empty commit with the contents of the e-mail\n\tmessage as its log message.\n\nor something.\n\n> diff --git a/builtin/am.c b/builtin/am.c\n> index f45aa33366f..89a29ccac89 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)\n>  \t\t\t\tto_keep = 1;\n>  \t\t\t\tbreak;\n>  \t\t\tcase STOP_ON_EMPTY_COMMIT:\n> -\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n> +\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n> +\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n\nIt would be better to hide the new part of the message behind\nadvice_if_enabled(), and advise \"--skip\" first before giving\n\"--allow-empty\" as another possibility.  It is rather a common\nmistake to grab patches with the cover letter and see \"am\" die on\nthe cover letter.\n\nIf we were to add an advice message there, that is.  I am not sure\nif it is necessary, especially since we give them in \"git status\".\n\n> @@ -1898,21 +1899,34 @@ next:\n>  /**\n>   * Resume the current am session after patch application failure. The user did\n>   * all the hard work, and we do not have to do any patch application. Just\n> - * trust and commit what the user has in the index and working tree.\n> + * trust and commit what the user has in the index and working tree. If `allow_empty`\n> + * is true, commit as an empty commit when index has not changed and lacking a patch.\n>   */\n> -static void am_resolve(struct am_state *state)\n> +static void am_resolve(struct am_state *state, int allow_empty)\n>  {\n> +\tint index_changed;\n> +\n>  \tvalidate_resume_state(state);\n>  \n>  \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n>  \n> -\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n> -\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n> -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n> -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n> +\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n> +\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n\nOverlong line.\n\n> +\t\tprintf_ln(_(\"Invalid resume value.\"));\n\nIt is unclear to the user what \"resume value\" means.  I am guessing\nthat this is saying that the user gave us the \"--allow-empty\" option,\nbut it is an inappropriate option for the situation.  So\n\n\t\"--allow-empty is not appropriate in this situation\"\n\nwould be a slight improvement, but it is not clear what makes it\ninappropriate, iow, what the situation is, to the users.\n\nStepping back a bit, if we disect that overlong condition:\n\n\tif (allow_empty &&\n\t    (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n\nit seems to be computing something not quite right.  What we want\nwith \"allow\" empty is (1) if we have recorded changes to the index,\nthat is perfectly fine.  We'll create a non-empty commit, and (2) if\nwe do not have any change in the index because the input did not\nhave a patch, it is OK to create an empty commit out of the current\nindex.  But the condition is there only to complain about\n\"--allow-empty\" given when either (1) we have changes, or (2) when\nthe input had a patch.  These are conditions that we can internally\nignore allow_empty settings and let the existing code handle the\nmessage as before this patch.\n\n>  \t\tdie_user_resolve(state);\n\nAnd I do not see how \"when you have resolved with this problem, do one\nof these things\" message would help in this situation.  Perhaps we\ncan get rid of the above block.  We still need to compute index_changed,\nand make \"no changes--we will die\" conditional.\n\n> +\tif (!index_changed) {\n> +\t\tif (allow_empty)\n> +\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n\nA command to the user to \"record it as an empty commit\" is not what\nwe want to give.  Perhaps \"recorded it as ...\" would work better as\na report of what we did for the user.\n\nAlso style:\n\n\t\tif (allow_empty) {\n\t\t\tprintf_ln(...);\n\t\t} else {\n                \t... original multi-statement block come here ...\n\t\t}\n\nThat is, when one arm of if/else if/.../else cascade needs {}, put\n{} to all of them.\n\nI think there is a logic error here once we remove the way too\naggressive \"you may not say --allow-empty when there is a patch\"\ncheck we saw earlier.\n\nWhen we see no changes added to the index and \"--allow-empty\" given,\nwe shouldn't be blindly creating an empty commit without checking if\nwe didn't have any patch.  If the last failure that gave the control\nback from \"am\" to the user was a patch that did not apply, we do not\nwant to create an empty commit, do we?  So something like\n\n\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n\t\tif (allow_empty &&\n\t\t    is_empty_or_missing_file(am_path(state, \"patch\"))) {\n\t\t\tprintf_ln(_(\"No changes - recorded an empty commit.\"));\n\t\t} else {\n\t\t\t... original block ...\n\nperhaps?\n\n> +\t\telse {\n> +\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n> +\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n> +\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n> +\t\t\tdie_user_resolve(state);\n> +\t\t}\n> +\t}\n\n> @@ -2237,7 +2251,8 @@ enum resume_type {\n>  \tRESUME_SKIP,\n>  \tRESUME_ABORT,\n>  \tRESUME_QUIT,\n> -\tRESUME_SHOW_PATCH\n> +\tRESUME_SHOW_PATCH,\n> +\tRESUME_ALLOW_EMPTY\n\nAdvice on the trailing comma applies here equally.\n\n>  };\n\n> diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\n> index 7f2956d77ad..9309becfe03 100755\n> --- a/t/t7512-status-help.sh\n> +++ b/t/t7512-status-help.sh\n> @@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '\n>  On branch am_empty\n>  You are in the middle of an am session.\n>  The current patch is empty.\n> +  (use \"git am --allow-empty\" to record this patch as an empty commit)\n>    (use \"git am --skip\" to skip this patch)\n>    (use \"git am --abort\" to restore the original branch)\n\nYou do not want to add the new one the first.  \"--skip\" should come\nfirst, and perhaps this new one, and \"--abort\" at the end.\n\nThe general rule of thumb is to give common ones early before the\nless common ones and ones, and give ones with more severe\nconsequence the last.\n\n> diff --git a/wt-status.c b/wt-status.c\n> index 5d215f4e4f1..d578a0e9192 100644\n> --- a/wt-status.c\n> +++ b/wt-status.c\n> @@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,\n>  \t\tif (!s->state.am_empty_patch)\n>  \t\t\tstatus_printf_ln(s, color,\n>  \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n> +\t\telse\n> +\t\t\tstatus_printf_ln(s, color,\n> +\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n>  \t\tstatus_printf_ln(s, color,\n>  \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n>  \t\tstatus_printf_ln(s, color,\n"},{"id":"443326","messageId":"xmqqlf0w8e8i.fsf@gitster.g","threadId":"56877","inReplyTo":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","subject":"Re: [PATCH v17 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T18:24:45Z","receivedAt":"2021-12-07T18:24:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Aleen via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Since that git has supported the --always option for the git-format-patch\n> command to create a patch with an empty commit message, git-am should\n> support applying and committing with empty patches.\n> ...\n> Changes since v16:\n>\n>  1. fix typo from \"had\" to \"has\" in the comment.\n\nI read the patches in full again and saw some issues that want to be\nfixed, but overall it was a pleasant read.\n\nThanks.\n"},{"id":"443403","messageId":"pull.1076.v18.git.1638939946.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v17.git.1638865913.gitgitgadget@gmail.com","subject":"[PATCH v18 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T05:05:43Z","receivedAt":"2021-12-08T05:05:57Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n----------------------------------------------------------------------------\n\nChanges since v14:\n\n 1. reimplement the 'die' value and stop the whole session. (Expected a\n    reroll)\n 2. the --allow-empty option is a valid resume value only when: (Expected a\n    reroll)\n    * index has not changed\n    * lacking a patch\n\n----------------------------------------------------------------------------\n\nChanges since v15:\n\n 1. rename \"die\" to \"stop\".\n\n----------------------------------------------------------------------------\n\nChanges since v16:\n\n 1. fix the typo from \"had\" to \"has\" in the comment.\n\n----------------------------------------------------------------------------\n\nChanges since v17:\n\n 1. add trailing comma, show tips of creating an empty commit, and use \"%B\"\n    to construct test cases.\n 2. remove the error \"Invalid resume value.\".\n 3. hint \"--allow-empty\" after \"--skip\".\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           |  16 ++++-\n Documentation/git-format-patch.txt |   6 +-\n builtin/am.c                       |  96 ++++++++++++++++++++++----\n t/t4150-am.sh                      | 104 +++++++++++++++++++++++++++++\n t/t7512-status-help.sh             |   1 +\n wt-status.c                        |   8 ++-\n 6 files changed, 215 insertions(+), 16 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v18\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v18\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v17:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  b9e03f2342b ! 2:  56953d416d9 am: support --empty=<option> to handle empty patches\n     @@ builtin/am.c: enum show_patch_type {\n      +enum empty_action {\n      +\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n      +\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n     -+\tKEEP_EMPTY_COMMIT          /* keep recording as empty commits */\n     ++\tKEEP_EMPTY_COMMIT,         /* keep recording as empty commits */\n      +};\n      +\n       struct am_state {\n     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n      +\t\t\t\tbreak;\n      +\t\t\tcase KEEP_EMPTY_COMMIT:\n      +\t\t\t\tto_keep = 1;\n     ++\t\t\t\tsay(state, stdout, _(\"Creating an empty commit: %.*s\"),\n     ++\t\t\t\t\tlinelen(state->msg), state->msg);\n      +\t\t\t\tbreak;\n      +\t\t\tcase STOP_ON_EMPTY_COMMIT:\n      +\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '\n      +'\n      +\n      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n     -+\tgit am --empty=keep empty-commit.patch &&\n     ++\tgit am --empty=keep empty-commit.patch >output &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit show empty-commit --format=\"%s\" >expected &&\n     -+\tgit show HEAD --format=\"%s\" >actual &&\n     -+\ttest_cmp actual expected\n     ++\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgrep -f actual expected &&\n     ++\tgrep \"Creating an empty commit: empty commit\" output\n      +'\n      +\n       test_done\n 3:  ea2dc088b37 ! 3:  4c3077f9384 am: support --allow-empty to record specific empty patches\n     @@ Commit message\n          am: support --allow-empty to record specific empty patches\n      \n          This option helps to record specific empty patches in the middle\n     -    of an am session. However, it is a valid resume value only when:\n     +    of an am session, which does create empty commits only when:\n      \n              1. index has not changed\n              2. lacking a branch\n     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.\n       \n      +--allow-empty::\n      +\tAfter a patch failure on an input e-mail message lacking a patch,\n     -+\tthe user can still record the empty patch as an empty commit with\n     -+\tthe contents of the e-mail message as its log.\n     ++\tcreate an empty commit with the contents of the e-mail message\n     ++\tas its log message.\n      +\n       DISCUSSION\n       ----------\n       \n      \n       ## builtin/am.c ##\n     -@@ builtin/am.c: static void am_run(struct am_state *state, int resume)\n     - \t\t\t\tto_keep = 1;\n     - \t\t\t\tbreak;\n     - \t\t\tcase STOP_ON_EMPTY_COMMIT:\n     --\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n     -+\t\t\t\tprintf_ln(_(\"Patch is empty.\\n\"\n     -+\t\t\t\t\t    \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\"));\n     - \t\t\t\tdie_user_resolve(state);\n     - \t\t\t\tbreak;\n     - \t\t\t}\n     +@@ builtin/am.c: static void NORETURN die_user_resolve(const struct am_state *state)\n     + \n     + \t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n     + \t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n     ++\n     ++\t\tif (advice_enabled(ADVICE_AM_WORK_DIR) &&\n     ++\t\t    is_empty_or_missing_file(am_path(state, \"patch\")) &&\n     ++\t\t    !repo_index_has_changes(the_repository, NULL, NULL))\n     ++\t\t\tprintf_ln(_(\"To record the empty patch as an empty commit, run \\\"%s --allow-empty\\\".\"), cmdline);\n     ++\n     + \t\tprintf_ln(_(\"To restore the original branch and stop patching, run \\\"%s --abort\\\".\"), cmdline);\n     + \t}\n     + \n      @@ builtin/am.c: next:\n       /**\n        * Resume the current am session after patch application failure. The user did\n     @@ builtin/am.c: next:\n      -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n      -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n      +\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n     -+\tif (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, \"patch\")))) {\n     -+\t\tprintf_ln(_(\"Invalid resume value.\"));\n     ++\tif (allow_empty &&\n     ++\t    !(!index_changed && is_empty_or_missing_file(am_path(state, \"patch\"))))\n       \t\tdie_user_resolve(state);\n     - \t}\n     - \n     ++\n      +\tif (!index_changed) {\n     -+\t\tif (allow_empty)\n     -+\t\t\tprintf_ln(_(\"No changes - record it as an empty commit.\"));\n     -+\t\telse {\n     ++\t\tif (allow_empty) {\n     ++\t\t\tprintf_ln(_(\"No changes - recorded it as an empty commit.\"));\n     ++\t\t} else {\n      +\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n      +\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n      +\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n      +\t\t\tdie_user_resolve(state);\n      +\t\t}\n     -+\t}\n     -+\n     + \t}\n     + \n       \tif (unmerged_cache()) {\n     - \t\tprintf_ln(_(\"You still have unmerged paths in your index.\\n\"\n     - \t\t\t\"You should 'git add' each file with resolved conflicts to mark them as such.\\n\"\n      @@ builtin/am.c: enum resume_type {\n       \tRESUME_SKIP,\n       \tRESUME_ABORT,\n       \tRESUME_QUIT,\n      -\tRESUME_SHOW_PATCH\n      +\tRESUME_SHOW_PATCH,\n     -+\tRESUME_ALLOW_EMPTY\n     ++\tRESUME_ALLOW_EMPTY,\n       };\n       \n       struct resume_mode {\n     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)\n      \n       ## t/t4150-am.sh ##\n      @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mail message that\n     - \ttest_cmp actual expected\n     + \tgrep \"Creating an empty commit: empty commit\" output\n       '\n       \n      +test_expect_success 'skip an empty patch in the middle of an am session' '\n      +\tgit checkout empty-commit^ &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n      +\tgrep \"Patch is empty.\" err &&\n     -+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n     ++\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n      +\tgit am --skip &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n      +\tgit rev-parse empty-commit^ >expected &&\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\tgit checkout empty-commit^ &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n      +\tgrep \"Patch is empty.\" err &&\n     -+\tgrep \"If you want to record it as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n     -+\tgit am --allow-empty &&\n     ++\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n     ++\tgit am --allow-empty >output &&\n     ++\tgrep \"No changes - recorded it as an empty commit.\" output &&\n      +\ttest_path_is_missing .git/rebase-apply &&\n     -+\tgit show empty-commit --format=\"%s\" >expected &&\n     -+\tgit show HEAD --format=\"%s\" >actual &&\n     -+\ttest_cmp actual expected\n     ++\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgrep -f actual expected\n      +'\n      +\n      +test_expect_success 'cannot create empty commits when the index is changed' '\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\t: >empty-file &&\n      +\tgit add empty-file &&\n      +\ttest_must_fail git am --allow-empty >err &&\n     -+\tgrep \"Invalid resume value.\" err\n     ++\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err\n      +'\n      +\n      +test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\tgit rev-parse HEAD >expected &&\n      +\ttest_must_fail git am seq.patch &&\n      +\ttest_must_fail git am --allow-empty >err &&\n     -+\tgrep \"Invalid resume value.\" err &&\n     ++\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n      +\tgit rev-parse HEAD >actual &&\n      +\ttest_cmp actual expected\n      +'\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\tgit rev-parse HEAD >expected &&\n      +\ttest_must_fail git am -3 seq.patch &&\n      +\ttest_must_fail git am --allow-empty >err &&\n     -+\tgrep \"Invalid resume value.\" err &&\n     ++\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n      +\tgit rev-parse HEAD >actual &&\n      +\ttest_cmp actual expected\n      +'\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n       test_done\n      \n       ## t/t7512-status-help.sh ##\n     -@@ t/t7512-status-help.sh: test_expect_success 'status in an am session: empty patch' '\n     - On branch am_empty\n     +@@ t/t7512-status-help.sh: On branch am_empty\n       You are in the middle of an am session.\n       The current patch is empty.\n     -+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n         (use \"git am --skip\" to skip this patch)\n     ++  (use \"git am --allow-empty\" to record this patch as an empty commit)\n         (use \"git am --abort\" to restore the original branch)\n       \n     + nothing to commit (use -u to show untracked files)\n      \n       ## wt-status.c ##\n     -@@ wt-status.c: static void show_am_in_progress(struct wt_status *s,\n     - \t\tif (!s->state.am_empty_patch)\n     +@@ wt-status.c: static void show_merge_in_progress(struct wt_status *s,\n     + static void show_am_in_progress(struct wt_status *s,\n     + \t\t\t\tconst char *color)\n     + {\n     ++\tint am_empty_patch;\n     ++\n     + \tstatus_printf_ln(s, color,\n     + \t\t_(\"You are in the middle of an am session.\"));\n     + \tif (s->state.am_empty_patch)\n     + \t\tstatus_printf_ln(s, color,\n     + \t\t\t_(\"The current patch is empty.\"));\n     + \tif (s->hints) {\n     +-\t\tif (!s->state.am_empty_patch)\n     ++\t\tam_empty_patch = s->state.am_empty_patch;\n     ++\t\tif (!am_empty_patch)\n       \t\t\tstatus_printf_ln(s, color,\n       \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n     -+\t\telse\n     -+\t\t\tstatus_printf_ln(s, color,\n     -+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n       \t\tstatus_printf_ln(s, color,\n       \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n     ++\t\tif (am_empty_patch)\n     ++\t\t\tstatus_printf_ln(s, color,\n     ++\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n       \t\tstatus_printf_ln(s, color,\n     + \t\t\t_(\"  (use \\\"git am --abort\\\" to restore the original branch)\"));\n     + \t}\n\n-- \ngitgitgadget\n"},{"id":"443404","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1638939946.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v18.git.1638939946.gitgitgadget@gmail.com","subject":"[PATCH v18 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T05:05:44Z","receivedAt":"2021-12-08T05:05:58Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"443405","messageId":"56953d416d99fa01f0011ced3ce3289f0f7c7623.1638939946.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v18.git.1638939946.gitgitgadget@gmail.com","subject":"[PATCH v18 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T05:05:45Z","receivedAt":"2021-12-08T05:05:59Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('stop', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 57 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..7676bd58ae7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(stop|drop|keep)::\n+\tBy default, or when the option is set to 'stop', the command\n+\terrors out on an input e-mail message lacking a patch\n+\tand stops into the middle of the current am session. When this\n+\toption is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..7a66ad23737 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n+\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT,         /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"stop\"))\n+\t\t*opt_value = STOP_ON_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,29 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tsay(state, stdout, _(\"Creating an empty commit: %.*s\"),\n+\t\t\t\t\tlinelen(state->msg), state->msg);\n+\t\t\t\tbreak;\n+\t\t\tcase STOP_ON_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1870,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2401,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{stop,drop,keep}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..f2a7a68eda0 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,48 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=stop\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=stop empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch >output &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected &&\n+\tgrep \"Creating an empty commit: empty commit\" output\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443406","messageId":"4c3077f938435508850727e05ad514035f09bebb.1638939946.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v18.git.1638939946.gitgitgadget@gmail.com","subject":"[PATCH v18 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-08T05:05:46Z","receivedAt":"2021-12-08T05:06:00Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session, which does create empty commits only when:\n\n    1. index has not changed\n    2. lacking a branch\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 +++++-\n builtin/am.c             | 39 +++++++++++++++++++++++------\n t/t4150-am.sh            | 54 ++++++++++++++++++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  8 +++++-\n 5 files changed, 99 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 7676bd58ae7..09107fb1067 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tcreate an empty commit with the contents of the e-mail message\n+\tas its log message.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex 7a66ad23737..d73e415bbb0 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)\n \n \t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n \t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n+\n+\t\tif (advice_enabled(ADVICE_AM_WORK_DIR) &&\n+\t\t    is_empty_or_missing_file(am_path(state, \"patch\")) &&\n+\t\t    !repo_index_has_changes(the_repository, NULL, NULL))\n+\t\t\tprintf_ln(_(\"To record the empty patch as an empty commit, run \\\"%s --allow-empty\\\".\"), cmdline);\n+\n \t\tprintf_ln(_(\"To restore the original branch and stop patching, run \\\"%s --abort\\\".\"), cmdline);\n \t}\n \n@@ -1900,19 +1906,31 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when index has not changed and lacking a patch.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n+\tint index_changed;\n+\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n-\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n+\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n+\tif (allow_empty &&\n+\t    !(!index_changed && is_empty_or_missing_file(am_path(state, \"patch\"))))\n \t\tdie_user_resolve(state);\n+\n+\tif (!index_changed) {\n+\t\tif (allow_empty) {\n+\t\t\tprintf_ln(_(\"No changes - recorded it as an empty commit.\"));\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tif (unmerged_cache()) {\n@@ -2239,7 +2257,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY,\n };\n \n struct resume_mode {\n@@ -2392,6 +2411,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2500,7 +2522,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex f2a7a68eda0..6b23384685b 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1202,4 +1202,58 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \tgrep \"Creating an empty commit: empty commit\" output\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty >output &&\n+\tgrep \"No changes - recorded it as an empty commit.\" output &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when the index is changed' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\t: >empty-file &&\n+\tgit add empty-file &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err\n+'\n+\n+test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am -3 seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..2f16d5787ed 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -659,6 +659,7 @@ On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n   (use \"git am --skip\" to skip this patch)\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --abort\" to restore the original branch)\n \n nothing to commit (use -u to show untracked files)\ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..335e723a71e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1218,17 +1218,23 @@ static void show_merge_in_progress(struct wt_status *s,\n static void show_am_in_progress(struct wt_status *s,\n \t\t\t\tconst char *color)\n {\n+\tint am_empty_patch;\n+\n \tstatus_printf_ln(s, color,\n \t\t_(\"You are in the middle of an am session.\"));\n \tif (s->state.am_empty_patch)\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"The current patch is empty.\"));\n \tif (s->hints) {\n-\t\tif (!s->state.am_empty_patch)\n+\t\tam_empty_patch = s->state.am_empty_patch;\n+\t\tif (!am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n+\t\tif (am_empty_patch)\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --abort\\\" to restore the original branch)\"));\n \t}\n-- \ngitgitgadget\n"},{"id":"443408","messageId":"xmqqmtlb4nvm.fsf@gitster.g","threadId":"56877","inReplyTo":"4c3077f938435508850727e05ad514035f09bebb.1638939946.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v18 3/3] am: support --allow-empty to record specific empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-08T06:22:21Z","receivedAt":"2021-12-08T06:22:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"\"徐沛文 (Aleen)\" via GitGitGadget\"  <gitgitgadget@gmail.com>\nwrites:\n\n> @@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)\n>  \n>  \t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n>  \t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n> +\n> +\t\tif (advice_enabled(ADVICE_AM_WORK_DIR) &&\n> +\t\t    is_empty_or_missing_file(am_path(state, \"patch\")) &&\n> +\t\t    !repo_index_has_changes(the_repository, NULL, NULL))\n> +\t\t\tprintf_ln(_(\"To record the empty patch as an empty commit, run \\\"%s --allow-empty\\\".\"), cmdline);\n> +\n\nOK, so the idea is when we give back control to the user, if the\nreason we stopped is because we did not have a usable patch in the\nmessage and we didn't have any change to the index, we can offer\n\"--allow-empty\" as an extra choice.\n\nI guess that makes sense.\n\n> @@ -1900,19 +1906,31 @@ next:\n>  /**\n>   * Resume the current am session after patch application failure. The user did\n>   * all the hard work, and we do not have to do any patch application. Just\n> - * trust and commit what the user has in the index and working tree.\n> + * trust and commit what the user has in the index and working tree. If `allow_empty`\n> + * is true, commit as an empty commit when index has not changed and lacking a patch.\n>   */\n> -static void am_resolve(struct am_state *state)\n> +static void am_resolve(struct am_state *state, int allow_empty)\n>  {\n> +\tint index_changed;\n> +\n>  \tvalidate_resume_state(state);\n>  \n>  \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n>  \n> -\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n> -\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n> -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n> -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n\n\n> +\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n> +\tif (allow_empty &&\n> +\t    !(!index_changed && is_empty_or_missing_file(am_path(state, \"patch\"))))\n>  \t\tdie_user_resolve(state);\n\nWhy do we need this?  \n\nIntuitively, because \"--allow-XYZ\" is \"we normally die when the\ncondition XYZ holds, but with the option, we do not die in such a\ncase\", any new condition that leads to \"die\" that only kicks in\nwhen \"--allow-XYZ\" is given smell like a BUG.\n\nThe condition reads:\n\n    unless we saw an empty patch (i.e. \"patch\" file is missing or\n    empty, and did not result in any change to the index), it is an\n    error to give \"--allow-empty\" to the command.\n\nThat somehow does not make any sense to me.  Whether failed patch\napplication and manual tweaking resulted in the same tree as HEAD,\nor a tree different from HEAD, if the user wants to say \"I allow\nGit to create an empty commit as necessary, instead of stopping\",\nshouldn't the user be allowed to?  After all, the option is not\n\"create nothing but an empty commit, it is an error if there is\nsomething to commit.\"  It merely allows creation of an empty one.\n\nThis series has been trying to add code to punish users who give\n\"--allow-empty\" when the command would have worked the same way\nwithout it at least since the last round, and I am not sure where\nthat wish to stop users comes from.  Please explain.  I am starting\nto think I may be missing something and this extra rigidity may be\nhelping a user (but again, I do not see how).\n\n> +\tif (!index_changed) {\n> +\t\tif (allow_empty) {\n\nThe index_changed variable being false is \"the result is an empty\nchange\", and is-empty-or-missing on \"patch\" file is \"the input is an\nempty change\".  Ideally we would want to create an empty commit only\nwhen both input and result are empty, but in order to prevent mistakes,\nwe may want to react to the case where the result is empty but the\ninput is not empty.\n\nHere is where we can use is-empty-or-missing on \"patch\" and give a\ndifferent message (i.e. \"even though the patch file is not empty,\nthe result of your patch application did not change anything\"), if\nwe wanted to.  Or you could choose to reject such an attempt, which\nmight be simpler.  I.e.\n\n\t\tif (allow_empty &&\n\t\t    is_empty_or_missing_file(am_path(state, \"patch\"))\n\t\t\t\"No changes - recorded an empty commit.\"\n\t\telse\n\t\t\t... the original error for no changes ...\n\n\nHmm?\n"},{"id":"443409","messageId":"220a9954.252.17d98caf6ad.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"xmqqmtlb4nvm.fsf@gitster.g","subject":"Re: [PATCH v18 3/3] am: support --allow-empty to record specific empty patches","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-12-08T06:46:18Z","receivedAt":"2021-12-08T06:46:24Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"\n> > +\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n> > +\tif (allow_empty &&\n> > +\t    !(!index_changed && is_empty_or_missing_file(am_path(state, \"patch\"))))\n> >  \t\tdie_user_resolve(state);\n> \n> Why do we need this?  \n> \n> Intuitively, because \"--allow-XYZ\" is \"we normally die when the\n> condition XYZ holds, but with the option, we do not die in such a\n> case\", any new condition that leads to \"die\" that only kicks in\n> when \"--allow-XYZ\" is given smell like a BUG.\n> \n> The condition reads:\n> \n>     unless we saw an empty patch (i.e. \"patch\" file is missing or\n>     empty, and did not result in any change to the index), it is an\n>     error to give \"--allow-empty\" to the command.\n> \n> That somehow does not make any sense to me.  Whether failed patch\n> application and manual tweaking resulted in the same tree as HEAD,\n> or a tree different from HEAD, if the user wants to say \"I allow\n> Git to create an empty commit as necessary, instead of stopping\",\n> shouldn't the user be allowed to?  After all, the option is not\n> \"create nothing but an empty commit, it is an error if there is\n> something to commit.\"  It merely allows creation of an empty one.\n> \n> This series has been trying to add code to punish users who give\n> \"--allow-empty\" when the command would have worked the same way\n> without it at least since the last round, and I am not sure where\n> that wish to stop users comes from.  Please explain.  I am starting\n> to think I may be missing something and this extra rigidity may be\n> helping a user (but again, I do not see how).\n\nIf there is no such code to check, the user can use \"--allow-empty\" to do the same thing\nas \"--continue\" or \"--resolved\" after resolving conflicts because they all go into `am_resolve()`.\nIn my opinion, \"--allow-empty\" only makes sense to allow the user to create empty commits when:\n\n    1. the result is an empty change (`!index_changed`)\n    2. the input is an empty change (`is_empty_or_missing_file(am_path(state, \"patch\"))`)\n\n> > +\tif (!index_changed) {\n> > +\t\tif (allow_empty) {\n> \n> The index_changed variable being false is \"the result is an empty\n> change\", and is-empty-or-missing on \"patch\" file is \"the input is an\n> empty change\".  Ideally we would want to create an empty commit only\n> when both input and result are empty, but in order to prevent mistakes,\n> we may want to react to the case where the result is empty but the\n> input is not empty.\n> \n> Here is where we can use is-empty-or-missing on \"patch\" and give a\n> different message (i.e. \"even though the patch file is not empty,\n> the result of your patch application did not change anything\"), if\n> we wanted to.  Or you could choose to reject such an attempt, which\n> might be simpler.  I.e.\n> \n> \t\tif (allow_empty &&\n> \t\t    is_empty_or_missing_file(am_path(state, \"patch\"))\n> \t\t\t\"No changes - recorded an empty commit.\"\n> \t\telse\n> \t\t\t... the original error for no changes ...\n> \n> \n> Hmm?\n\nBased on the previous checking, there is no need to check\n`is_empty_or_missing_file(am_path(state, \"patch\"))` here because it is permanently true.\n"},{"id":"443418","messageId":"xmqq7dcf49ii.fsf@gitster.g","threadId":"56877","inReplyTo":"220a9954.252.17d98caf6ad.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v18 3/3] am: support --allow-empty to record specific empty patches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-08T11:32:37Z","receivedAt":"2021-12-08T11:32:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aleen 徐沛文 <pwxu@coremail.cn> writes:\n\n>> This series has been trying to add code to punish users who give\n>> \"--allow-empty\" when the command would have worked the same way\n>> without it at least since the last round, and I am not sure where\n>> that wish to stop users comes from.  Please explain.  I am starting\n>> to think I may be missing something and this extra rigidity may be\n>> helping a user (but again, I do not see how).\n>\n> If there is no such code to check, the user can use\n> \"--allow-empty\" to do the same thing as \"--continue\" or\n> \"--resolved\" after resolving conflicts because they all go into\n> `am_resolve()`.  \n\nYes, exactly.  That is the point of \"allow\"; we continue normally,\nand even in a narrow situation (i.e. input is empty, and no change\nis made to the index) where we normally stop, we allow it to\ncontinue by creating an empty commit.\n\n> In my opinion, \"--allow-empty\" only makes sense\n> to allow the user to create empty commits when:\n>\n>     1. the result is an empty change (`!index_changed`)\n>     2. the input is an empty change (`is_empty_or_missing_file(am_path(state, \"patch\"))`)\n\nThen you are forcing the user to use it ONLY whe nhe input is empty\nand no other time.  Whatever that is, that is not \"allow\" empty.\n\n>> Here is where we can use is-empty-or-missing on \"patch\" and give a\n>> different message (i.e. \"even though the patch file is not empty,\n>> the result of your patch application did not change anything\"), if\n>> we wanted to.  Or you could choose to reject such an attempt, which\n>> might be simpler.  I.e.\n>> \n>> \t\tif (allow_empty &&\n>> \t\t    is_empty_or_missing_file(am_path(state, \"patch\"))\n>> \t\t\t\"No changes - recorded an empty commit.\"\n>> \t\telse\n>> \t\t\t... the original error for no changes ...\n>> \n>> \n>> Hmm?\n>\n> Based on the previous checking, there is no need to check\n> `is_empty_or_missing_file(am_path(state, \"patch\"))` here because it is permanently true.\n\nI think you misunderstood.\n\nIf we correct the overly strict check that makes \"--allow-empty\" to\nmean \"only create empty, anything else is an error\", the \"is patch\nan empty file?\" must be checked here.\n\n"},{"id":"443608","messageId":"pull.1076.v19.git.1639034755.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v18.git.1638939946.gitgitgadget@gmail.com","subject":"[PATCH v19 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches","fromName":"Aleen via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-09T07:25:52Z","receivedAt":"2021-12-09T07:26:00Z","isPatch":true,"sender":{"key":"aleen42@vip.qq.com","avatar":"https://avatars.githubusercontent.com/u/9573300?v=4"},"body":"Since that git has supported the --always option for the git-format-patch\ncommand to create a patch with an empty commit message, git-am should\nsupport applying and committing with empty patches.\n\n----------------------------------------------------------------------------\n\nChanges since v1:\n\n 1. add a case when not passing the --always option.\n 2. rename the --always option to --allow-empty.\n\n----------------------------------------------------------------------------\n\nChanges since v2:\n\n 1. rename the --allow-empty option to --empty-commit.\n 2. introduce three different strategies (die|skip|asis) when trying to\n    record empty patches as empty commits.\n\n----------------------------------------------------------------------------\n\nChanges since v3:\n\n 1. generate the missed file for test cases.\n 2. grep -f cannot be used under Mac OS.\n\n----------------------------------------------------------------------------\n\nChanges since v4:\n\n 1. rename the --empty-commit option to --empty.\n 2. rename three different strategies (die|skip|asis) to die, drop and keep\n    correspondingly.\n\n----------------------------------------------------------------------------\n\nChanges since v5:\n\n 1. throw an error when passing --empty option without value.\n\n----------------------------------------------------------------------------\n\nChanges since v6:\n\n 1. add i18n resources.\n\n----------------------------------------------------------------------------\n\nChanges since v7:\n\n 1. update code according to the seen branch.\n 2. fix the wrong document of git-am.\n 3. sign off commits by a real name.\n\n----------------------------------------------------------------------------\n\nChanges since v8:\n\n 1. update the committer's name with my real name to fix DCO of GGG.\n\n----------------------------------------------------------------------------\n\nChanges since v9:\n\n 1. imitate the signed name format of\n    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com\n    .\n\n----------------------------------------------------------------------------\n\nChanges since v11:\n\n 1. introduce an interactive option --allow-empty for git-am to record empty\n    patches in the middle of an am session.\n\n----------------------------------------------------------------------------\n\nChanges since v12:\n\n 1. record the empty patch as an empty commit only when there are no\n    changes.\n 2. fix indentation problems.\n 3. simplify \"to keep recording\" to \"to record\".\n 4. add a test case for skipping empty patches via the --skip option.\n\n----------------------------------------------------------------------------\n\nChanges since v13:\n\n 1. add an additional description about the 'die' value.\n\n----------------------------------------------------------------------------\n\nChanges since v14:\n\n 1. reimplement the 'die' value and stop the whole session. (Expected a\n    reroll)\n 2. the --allow-empty option is a valid resume value only when: (Expected a\n    reroll)\n    * index has not changed\n    * lacking a patch\n\n----------------------------------------------------------------------------\n\nChanges since v15:\n\n 1. rename \"die\" to \"stop\".\n\n----------------------------------------------------------------------------\n\nChanges since v16:\n\n 1. fix the typo from \"had\" to \"has\" in the comment.\n\n----------------------------------------------------------------------------\n\nChanges since v17:\n\n 1. add trailing comma, show tips of creating an empty commit, and use \"%B\"\n    to construct test cases.\n 2. remove the error \"Invalid resume value.\".\n 3. hint \"--allow-empty\" after \"--skip\".\n\n----------------------------------------------------------------------------\n\nChanges since v18:\n\n 1. remove strict checking of \"--allow-empty\".\n\n徐沛文 (Aleen) (3):\n  doc: git-format-patch: describe the option --always\n  am: support --empty=<option> to handle empty patches\n  am: support --allow-empty to record specific empty patches\n\n Documentation/git-am.txt           |  16 ++++-\n Documentation/git-format-patch.txt |   6 +-\n builtin/am.c                       |  89 ++++++++++++++++++++----\n t/t4150-am.sh                      | 107 +++++++++++++++++++++++++++++\n t/t7512-status-help.sh             |   1 +\n wt-status.c                        |   8 ++-\n 6 files changed, 211 insertions(+), 16 deletions(-)\n\n\nbase-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v19\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v19\nPull-Request: https://github.com/gitgitgadget/git/pull/1076\n\nRange-diff vs v18:\n\n 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always\n 2:  56953d416d9 = 2:  56953d416d9 am: support --empty=<option> to handle empty patches\n 3:  4c3077f9384 ! 3:  37875e8d313 am: support --allow-empty to record specific empty patches\n     @@ Commit message\n          This option helps to record specific empty patches in the middle\n          of an am session, which does create empty commits only when:\n      \n     -        1. index has not changed\n     +        1. the index has not changed\n              2. lacking a branch\n      \n     +    When the index has changed, \"--allow-empty\" will create a non-empty\n     +    commit like passing \"--continue\" or \"--resolved\".\n     +\n          Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n      \n       ## Documentation/git-am.txt ##\n     @@ builtin/am.c: next:\n      -static void am_resolve(struct am_state *state)\n      +static void am_resolve(struct am_state *state, int allow_empty)\n       {\n     -+\tint index_changed;\n     -+\n       \tvalidate_resume_state(state);\n       \n       \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n       \n     --\tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n     + \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n      -\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n      -\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n      -\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n     -+\tindex_changed = repo_index_has_changes(the_repository, NULL, NULL);\n     -+\tif (allow_empty &&\n     -+\t    !(!index_changed && is_empty_or_missing_file(am_path(state, \"patch\"))))\n     - \t\tdie_user_resolve(state);\n     -+\n     -+\tif (!index_changed) {\n     -+\t\tif (allow_empty) {\n     +-\t\tdie_user_resolve(state);\n     ++\t\tif (allow_empty && is_empty_or_missing_file(am_path(state, \"patch\"))) {\n      +\t\t\tprintf_ln(_(\"No changes - recorded it as an empty commit.\"));\n      +\t\t} else {\n      +\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai\n      +\tgrep -f actual expected\n      +'\n      +\n     -+test_expect_success 'cannot create empty commits when the index is changed' '\n     ++test_expect_success 'create an non-empty commit when the index IS changed though \"--allow-empty\" is given' '\n      +\tgit checkout empty-commit^ &&\n      +\ttest_must_fail git am empty-commit.patch >err &&\n      +\t: >empty-file &&\n      +\tgit add empty-file &&\n     -+\ttest_must_fail git am --allow-empty >err &&\n     -+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err\n     ++\tgit am --allow-empty &&\n     ++\tgit show empty-commit --format=\"%B\" >expected &&\n     ++\tgit show HEAD --format=\"%B\" >actual &&\n     ++\tgrep -f actual expected &&\n     ++\tgit diff HEAD^..HEAD --name-only\n      +'\n      +\n      +test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n\n-- \ngitgitgadget\n"},{"id":"443609","messageId":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1639034755.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v19.git.1639034755.gitgitgadget@gmail.com","subject":"[PATCH v19 1/3] doc: git-format-patch: describe the option --always","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-09T07:25:53Z","receivedAt":"2021-12-09T07:26:02Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis commit has described how to use '--always' option in the command\n'git-format-patch' to include patches for commits that emit no changes.\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-format-patch.txt | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex 113eabc107c..be797d7a28f 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t\t   [-n | --numbered | -N | --no-numbered]\n \t\t   [--start-number <n>] [--numbered-files]\n \t\t   [--in-reply-to=<message id>] [--suffix=.<sfx>]\n-\t\t   [--ignore-if-in-upstream]\n+\t\t   [--ignore-if-in-upstream] [--always]\n \t\t   [--cover-from-description=<mode>]\n \t\t   [--rfc] [--subject-prefix=<subject prefix>]\n \t\t   [(--reroll-count|-v) <n>]\n@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.\n \tpatches being generated, and any patch that matches is\n \tignored.\n \n+--always::\n+\tInclude patches for commits that do not introduce any change,\n+\twhich are omitted by default.\n+\n --cover-from-description=<mode>::\n \tControls which parts of the cover letter will be automatically\n \tpopulated using the branch's description.\n-- \ngitgitgadget\n\n"},{"id":"443610","messageId":"56953d416d99fa01f0011ced3ce3289f0f7c7623.1639034755.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v19.git.1639034755.gitgitgadget@gmail.com","subject":"[PATCH v19 2/3] am: support --empty=<option> to handle empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-09T07:25:54Z","receivedAt":"2021-12-09T07:26:03Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nSince that the command 'git-format-patch' can include patches of\ncommits that emit no changes, the 'git-am' command should also\nsupport an option, named as '--empty', to specify how to handle\nthose empty patches. In this commit, we have implemented three\nvalid options ('stop', 'drop' and 'keep').\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  9 +++++++\n builtin/am.c             | 57 ++++++++++++++++++++++++++++++++++++----\n t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 0a4a984dfde..7676bd58ae7 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -16,6 +16,7 @@ SYNOPSIS\n \t [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]\n \t [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]\n \t [--quoted-cr=<action>]\n+\t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n \n@@ -63,6 +64,14 @@ OPTIONS\n --quoted-cr=<action>::\n \tThis flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).\n \n+--empty=(stop|drop|keep)::\n+\tBy default, or when the option is set to 'stop', the command\n+\terrors out on an input e-mail message lacking a patch\n+\tand stops into the middle of the current am session. When this\n+\toption is set to 'drop', skip such an e-mail message instead.\n+\tWhen this option is set to 'keep', create an empty commit,\n+\trecording the contents of the e-mail message as its log.\n+\n -m::\n --message-id::\n \tPass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..7a66ad23737 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -87,6 +87,12 @@ enum show_patch_type {\n \tSHOW_PATCH_DIFF = 1,\n };\n \n+enum empty_action {\n+\tSTOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */\n+\tDROP_EMPTY_COMMIT,         /* skip with a notice message, unless \"--quiet\" has been passed */\n+\tKEEP_EMPTY_COMMIT,         /* keep recording as empty commits */\n+};\n+\n struct am_state {\n \t/* state directory path */\n \tchar *dir;\n@@ -118,6 +124,7 @@ struct am_state {\n \tint message_id;\n \tint scissors; /* enum scissors_type */\n \tint quoted_cr; /* enum quoted_cr_action */\n+\tint empty_type; /* enum empty_action */\n \tstruct strvec git_apply_opts;\n \tconst char *resolvemsg;\n \tint committer_date_is_author_date;\n@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,\n \treturn 0;\n }\n \n+static int am_option_parse_empty(const struct option *opt,\n+\t\t\t\t     const char *arg, int unset)\n+{\n+\tint *opt_value = opt->value;\n+\n+\tBUG_ON_OPT_NEG(unset);\n+\n+\tif (!strcmp(arg, \"stop\"))\n+\t\t*opt_value = STOP_ON_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"drop\"))\n+\t\t*opt_value = DROP_EMPTY_COMMIT;\n+\telse if (!strcmp(arg, \"keep\"))\n+\t\t*opt_value = KEEP_EMPTY_COMMIT;\n+\telse\n+\t\treturn error(_(\"Invalid value for --empty: %s\"), arg);\n+\n+\treturn 0;\n+}\n+\n /**\n  * Returns path relative to the am_state directory.\n  */\n@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)\n \t\tgoto finish;\n \t}\n \n-\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n-\t\tprintf_ln(_(\"Patch is empty.\"));\n-\t\tdie_user_resolve(state);\n-\t}\n-\n \tstrbuf_addstr(&msg, \"\\n\\n\");\n \tstrbuf_addbuf(&msg, &mi.log_message);\n \tstrbuf_stripspace(&msg, 0);\n@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)\n \twhile (state->cur <= state->last) {\n \t\tconst char *mail = am_path(state, msgnum(state));\n \t\tint apply_status;\n+\t\tint to_keep;\n \n \t\treset_ident_date();\n \n@@ -1792,8 +1814,29 @@ static void am_run(struct am_state *state, int resume)\n \t\tif (state->interactive && do_interactive(state))\n \t\t\tgoto next;\n \n+\t\tto_keep = 0;\n+\t\tif (is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tswitch (state->empty_type) {\n+\t\t\tcase DROP_EMPTY_COMMIT:\n+\t\t\t\tsay(state, stdout, _(\"Skipping: %.*s\"), linelen(state->msg), state->msg);\n+\t\t\t\tgoto next;\n+\t\t\t\tbreak;\n+\t\t\tcase KEEP_EMPTY_COMMIT:\n+\t\t\t\tto_keep = 1;\n+\t\t\t\tsay(state, stdout, _(\"Creating an empty commit: %.*s\"),\n+\t\t\t\t\tlinelen(state->msg), state->msg);\n+\t\t\t\tbreak;\n+\t\t\tcase STOP_ON_EMPTY_COMMIT:\n+\t\t\t\tprintf_ln(_(\"Patch is empty.\"));\n+\t\t\t\tdie_user_resolve(state);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n \t\tif (run_applypatch_msg_hook(state))\n \t\t\texit(1);\n+\t\tif (to_keep)\n+\t\t\tgoto commit;\n \n \t\tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n@@ -1827,6 +1870,7 @@ static void am_run(struct am_state *state, int resume)\n \t\t\tdie_user_resolve(state);\n \t\t}\n \n+commit:\n \t\tdo_commit(state);\n \n next:\n@@ -2357,6 +2401,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 'S', \"gpg-sign\", &state.sign_commit, N_(\"key-id\"),\n \t\t  N_(\"GPG-sign commits\"),\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n+\t\tOPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, \"empty\", &state.empty_type, \"{stop,drop,keep}\",\n+\t\t  N_(\"how to handle empty patches\"),\n+\t\t  PARSE_OPT_NONEG, am_option_parse_empty),\n \t\tOPT_HIDDEN_BOOL(0, \"rebasing\", &state.rebasing,\n \t\t\tN_(\"(internal use for git-rebase)\")),\n \t\tOPT_END()\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex 2aaaa0d7ded..f2a7a68eda0 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -196,6 +196,12 @@ test_expect_success setup '\n \n \tgit format-patch -M --stdout lorem^ >rename-add.patch &&\n \n+\tgit checkout -b empty-commit &&\n+\tgit commit -m \"empty commit\" --allow-empty &&\n+\n+\t: >empty.patch &&\n+\tgit format-patch --always --stdout empty-commit^ >empty-commit.patch &&\n+\n \t# reset time\n \tsane_unset test_tick &&\n \ttest_tick\n@@ -1152,4 +1158,48 @@ test_expect_success 'apply binary blob in partial clone' '\n \tgit -C client am ../patch\n '\n \n+test_expect_success 'an empty input file is error regardless of --empty option' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=drop empty.patch 2>actual &&\n+\techo \"Patch format detection failed.\" >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'invalid when passing the --empty option alone' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am --empty empty-commit.patch 2>err &&\n+\techo \"error: Invalid value for --empty: empty-commit.patch\" >expected &&\n+\ttest_cmp expected err\n+'\n+\n+test_expect_success 'a message without a patch is an error (default)' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty\" err\n+'\n+\n+test_expect_success 'a message without a patch is an error where an explicit \"--empty=stop\" is given' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\ttest_must_fail git am --empty=stop empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err\n+'\n+\n+test_expect_success 'a message without a patch will be skipped when \"--empty=drop\" is given' '\n+\tgit am --empty=drop empty-commit.patch >output &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual &&\n+\tgrep \"Skipping: empty commit\" output\n+'\n+\n+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '\n+\tgit am --empty=keep empty-commit.patch >output &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected &&\n+\tgrep \"Creating an empty commit: empty commit\" output\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"443611","messageId":"37875e8d3130133ac3c897fddacc0bf186ba3b1c.1639034755.git.gitgitgadget@gmail.com","threadId":"56877","inReplyTo":"pull.1076.v19.git.1639034755.gitgitgadget@gmail.com","subject":"[PATCH v19 3/3] am: support --allow-empty to record specific empty patches","fromName":"徐沛文 (Aleen) via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-12-09T07:25:55Z","receivedAt":"2021-12-09T07:26:05Z","isPatch":true,"sender":{"key":"name:徐沛文 (Aleen)","avatar":null},"body":"From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=\n <aleen42@vip.qq.com>\n\nThis option helps to record specific empty patches in the middle\nof an am session, which does create empty commits only when:\n\n    1. the index has not changed\n    2. lacking a branch\n\nWhen the index has changed, \"--allow-empty\" will create a non-empty\ncommit like passing \"--continue\" or \"--resolved\".\n\nSigned-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>\n---\n Documentation/git-am.txt |  7 ++++-\n builtin/am.c             | 32 ++++++++++++++++------\n t/t4150-am.sh            | 57 ++++++++++++++++++++++++++++++++++++++++\n t/t7512-status-help.sh   |  1 +\n wt-status.c              |  8 +++++-\n 5 files changed, 95 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-am.txt b/Documentation/git-am.txt\nindex 7676bd58ae7..09107fb1067 100644\n--- a/Documentation/git-am.txt\n+++ b/Documentation/git-am.txt\n@@ -18,7 +18,7 @@ SYNOPSIS\n \t [--quoted-cr=<action>]\n \t [--empty=(stop|drop|keep)]\n \t [(<mbox> | <Maildir>)...]\n-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])\n+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)\n \n DESCRIPTION\n -----------\n@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.\n \tthe e-mail message; if `diff`, show the diff portion only.\n \tDefaults to `raw`.\n \n+--allow-empty::\n+\tAfter a patch failure on an input e-mail message lacking a patch,\n+\tcreate an empty commit with the contents of the e-mail message\n+\tas its log message.\n+\n DISCUSSION\n ----------\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex 7a66ad23737..f2e7e533881 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)\n \n \t\tprintf_ln(_(\"When you have resolved this problem, run \\\"%s --continue\\\".\"), cmdline);\n \t\tprintf_ln(_(\"If you prefer to skip this patch, run \\\"%s --skip\\\" instead.\"), cmdline);\n+\n+\t\tif (advice_enabled(ADVICE_AM_WORK_DIR) &&\n+\t\t    is_empty_or_missing_file(am_path(state, \"patch\")) &&\n+\t\t    !repo_index_has_changes(the_repository, NULL, NULL))\n+\t\t\tprintf_ln(_(\"To record the empty patch as an empty commit, run \\\"%s --allow-empty\\\".\"), cmdline);\n+\n \t\tprintf_ln(_(\"To restore the original branch and stop patching, run \\\"%s --abort\\\".\"), cmdline);\n \t}\n \n@@ -1900,19 +1906,24 @@ next:\n /**\n  * Resume the current am session after patch application failure. The user did\n  * all the hard work, and we do not have to do any patch application. Just\n- * trust and commit what the user has in the index and working tree.\n+ * trust and commit what the user has in the index and working tree. If `allow_empty`\n+ * is true, commit as an empty commit when index has not changed and lacking a patch.\n  */\n-static void am_resolve(struct am_state *state)\n+static void am_resolve(struct am_state *state, int allow_empty)\n {\n \tvalidate_resume_state(state);\n \n \tsay(state, stdout, _(\"Applying: %.*s\"), linelen(state->msg), state->msg);\n \n \tif (!repo_index_has_changes(the_repository, NULL, NULL)) {\n-\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n-\t\t\t\"If there is nothing left to stage, chances are that something else\\n\"\n-\t\t\t\"already introduced the same changes; you might want to skip this patch.\"));\n-\t\tdie_user_resolve(state);\n+\t\tif (allow_empty && is_empty_or_missing_file(am_path(state, \"patch\"))) {\n+\t\t\tprintf_ln(_(\"No changes - recorded it as an empty commit.\"));\n+\t\t} else {\n+\t\t\tprintf_ln(_(\"No changes - did you forget to use 'git add'?\\n\"\n+\t\t\t\t    \"If there is nothing left to stage, chances are that something else\\n\"\n+\t\t\t\t    \"already introduced the same changes; you might want to skip this patch.\"));\n+\t\t\tdie_user_resolve(state);\n+\t\t}\n \t}\n \n \tif (unmerged_cache()) {\n@@ -2239,7 +2250,8 @@ enum resume_type {\n \tRESUME_SKIP,\n \tRESUME_ABORT,\n \tRESUME_QUIT,\n-\tRESUME_SHOW_PATCH\n+\tRESUME_SHOW_PATCH,\n+\tRESUME_ALLOW_EMPTY,\n };\n \n struct resume_mode {\n@@ -2392,6 +2404,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t  N_(\"show the patch being applied\"),\n \t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n+\t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n+\t\t\tN_(\"record the empty patch as an empty commit\"),\n+\t\t\tRESUME_ALLOW_EMPTY),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n \t\t\t&state.committer_date_is_author_date,\n \t\t\tN_(\"lie about committer date\")),\n@@ -2500,7 +2515,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\tam_run(&state, 1);\n \t\tbreak;\n \tcase RESUME_RESOLVED:\n-\t\tam_resolve(&state);\n+\tcase RESUME_ALLOW_EMPTY:\n+\t\tam_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);\n \t\tbreak;\n \tcase RESUME_SKIP:\n \t\tam_skip(&state);\ndiff --git a/t/t4150-am.sh b/t/t4150-am.sh\nindex f2a7a68eda0..2db2c5dfe58 100755\n--- a/t/t4150-am.sh\n+++ b/t/t4150-am.sh\n@@ -1202,4 +1202,61 @@ test_expect_success 'record as an empty commit when meeting e-mail message that\n \tgrep \"Creating an empty commit: empty commit\" output\n '\n \n+test_expect_success 'skip an empty patch in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --skip &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit rev-parse empty-commit^ >expected &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\tgrep \"Patch is empty.\" err &&\n+\tgrep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit am --allow-empty >output &&\n+\tgrep \"No changes - recorded it as an empty commit.\" output &&\n+\ttest_path_is_missing .git/rebase-apply &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected\n+'\n+\n+test_expect_success 'create an non-empty commit when the index IS changed though \"--allow-empty\" is given' '\n+\tgit checkout empty-commit^ &&\n+\ttest_must_fail git am empty-commit.patch >err &&\n+\t: >empty-file &&\n+\tgit add empty-file &&\n+\tgit am --allow-empty &&\n+\tgit show empty-commit --format=\"%B\" >expected &&\n+\tgit show HEAD --format=\"%B\" >actual &&\n+\tgrep -f actual expected &&\n+\tgit diff HEAD^..HEAD --name-only\n+'\n+\n+test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n+test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '\n+\ttest_when_finished \"git am --abort || :\" &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_must_fail git am -3 seq.patch &&\n+\ttest_must_fail git am --allow-empty >err &&\n+\t! grep \"To record the empty patch as an empty commit, run \\\"git am --allow-empty\\\".\" err &&\n+\tgit rev-parse HEAD >actual &&\n+\ttest_cmp actual expected\n+'\n+\n test_done\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 7f2956d77ad..2f16d5787ed 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -659,6 +659,7 @@ On branch am_empty\n You are in the middle of an am session.\n The current patch is empty.\n   (use \"git am --skip\" to skip this patch)\n+  (use \"git am --allow-empty\" to record this patch as an empty commit)\n   (use \"git am --abort\" to restore the original branch)\n \n nothing to commit (use -u to show untracked files)\ndiff --git a/wt-status.c b/wt-status.c\nindex 5d215f4e4f1..335e723a71e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -1218,17 +1218,23 @@ static void show_merge_in_progress(struct wt_status *s,\n static void show_am_in_progress(struct wt_status *s,\n \t\t\t\tconst char *color)\n {\n+\tint am_empty_patch;\n+\n \tstatus_printf_ln(s, color,\n \t\t_(\"You are in the middle of an am session.\"));\n \tif (s->state.am_empty_patch)\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"The current patch is empty.\"));\n \tif (s->hints) {\n-\t\tif (!s->state.am_empty_patch)\n+\t\tam_empty_patch = s->state.am_empty_patch;\n+\t\tif (!am_empty_patch)\n \t\t\tstatus_printf_ln(s, color,\n \t\t\t\t_(\"  (fix conflicts and then run \\\"git am --continue\\\")\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --skip\\\" to skip this patch)\"));\n+\t\tif (am_empty_patch)\n+\t\t\tstatus_printf_ln(s, color,\n+\t\t\t\t_(\"  (use \\\"git am --allow-empty\\\" to record this patch as an empty commit)\"));\n \t\tstatus_printf_ln(s, color,\n \t\t\t_(\"  (use \\\"git am --abort\\\" to restore the original branch)\"));\n \t}\n-- \ngitgitgadget\n"},{"id":"443624","messageId":"1ad4a3ee-af05-9bb2-67fe-566d5e4c39a8@gmail.com","threadId":"56877","inReplyTo":"a524ca6adfa2adc02e517b7be5199b0c071134c4.1639034755.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v19 1/3] doc: git-format-patch: describe the option --always","fromName":"Bagas Sanjaya","fromEmail":"bagasdotme@gmail.com","sentAt":"2021-12-09T09:28:14Z","receivedAt":"2021-12-09T09:28:32Z","isPatch":true,"sender":{"key":"bagasdotme@gmail.com","avatar":"https://avatars.githubusercontent.com/u/40219486?v=4"},"body":"On 09/12/21 14.25, 徐沛文 (Aleen) via GitGitGadget wrote:\n> +--always::\n> +\tInclude patches for commits that do not introduce any change,\n> +\twhich are omitted by default.\n> +\n\nWhat about this wording below?\n\ndiff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\nindex be797d7a28..302659cfd5 100644\n--- a/Documentation/git-format-patch.txt\n+++ b/Documentation/git-format-patch.txt\n@@ -193,8 +193,8 @@ will want to ensure that threading is disabled for `git send-email`.\n  \tignored.\n  \n  --always::\n-\tInclude patches for commits that do not introduce any change,\n-\twhich are omitted by default.\n+\tAlways generate patches, even if there are empty-change commits.\n+\tDefault is to omit such commits.\n  \n  --cover-from-description=<mode>::\n  \tControls which parts of the cover letter will be automatically\n\n\n-- \nAn old man doll... just what I always wanted! - Clara\n"},{"id":"443723","messageId":"7bd729fd.29.17da1f2df5d.Coremail.pwxu@coremail.cn","threadId":"56877","inReplyTo":"1ad4a3ee-af05-9bb2-67fe-566d5e4c39a8@gmail.com","subject":"Re: [PATCH v19 1/3] doc: git-format-patch: describe the option --always","fromName":"Aleen 徐沛文","fromEmail":"pwxu@coremail.cn","sentAt":"2021-12-10T01:26:28Z","receivedAt":"2021-12-10T01:26:34Z","isPatch":true,"sender":{"key":"pwxu@coremail.cn","avatar":null},"body":"> What about this wording below?\n> \n> diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt\n> index be797d7a28..302659cfd5 100644\n> --- a/Documentation/git-format-patch.txt\n> +++ b/Documentation/git-format-patch.txt\n> @@ -193,8 +193,8 @@ will want to ensure that threading is disabled for `git send-email`.\n>   \tignored.\n>   \n>   --always::\n> -\tInclude patches for commits that do not introduce any change,\n> -\twhich are omitted by default.\n> +\tAlways generate patches, even if there are empty-change commits.\n> +\tDefault is to omit such commits.\n>   \n>   --cover-from-description=<mode>::\n>   \tControls which parts of the cover letter will be automatically\n> \n> \n> -- \n> An old man doll... just what I always wanted! - Clara\n\nDears Sanjaya,\n\n    Thanks for your suggestion. I don't see the actual difference between\n    the two sentences, and do you want to enhance the word \"always\"?\n    If you do, how about just describing the option as \"Always include patches...\"?\n\nAleen"},{"id":"443747","messageId":"0784617f-416f-a981-44e0-62bd22018d6c@gmail.com","threadId":"56877","inReplyTo":"7bd729fd.29.17da1f2df5d.Coremail.pwxu@coremail.cn","subject":"Re: [PATCH v19 1/3] doc: git-format-patch: describe the option --always","fromName":"Bagas Sanjaya","fromEmail":"bagasdotme@gmail.com","sentAt":"2021-12-10T06:50:46Z","receivedAt":"2021-12-10T06:50:55Z","isPatch":true,"sender":{"key":"bagasdotme@gmail.com","avatar":"https://avatars.githubusercontent.com/u/40219486?v=4"},"body":"On 10/12/21 08.26, Aleen 徐沛文 wrote:\n> Dears Sanjaya,\n> \n>      Thanks for your suggestion. I don't see the actual difference between\n>      the two sentences, and do you want to enhance the word \"always\"?\n>      If you do, how about just describing the option as \"Always include patches...\"?\n> \n> Aleen\n> \n\nSemantically speaking, I choose \"generate\" because git format-patch **generates**\npatches from the specified commits.\n\n-- \nAn old man doll... just what I always wanted! - Clara\n"},{"id":"443879","messageId":"xmqqczm3lcma.fsf@gitster.g","threadId":"56877","inReplyTo":"0784617f-416f-a981-44e0-62bd22018d6c@gmail.com","subject":"Re: [PATCH v19 1/3] doc: git-format-patch: describe the option --always","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-11T09:22:37Z","receivedAt":"2021-12-11T09:22:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bagas Sanjaya <bagasdotme@gmail.com> writes:\n\n> On 10/12/21 08.26, Aleen 徐沛文 wrote:\n>> Dears Sanjaya,\n>>      Thanks for your suggestion. I don't see the actual difference\n>> between\n>>      the two sentences, and do you want to enhance the word \"always\"?\n>>      If you do, how about just describing the option as \"Always include patches...\"?\n>> Aleen\n>> \n>\n> Semantically speaking, I choose \"generate\" because git format-patch **generates**\n> patches from the specified commits.\n\nBut the given commit is empty and does not result in a patch at\nall, which is the whole point of having this option, no?  A typical\nformat-patch output message consists of the message part and the\npatch part, and calling an output created for an empty commit a\n\"patch\" leads to confusion, I am afraid.\n\nSo, between the two sentences, I do not see much difference either\nway.\n"}]}