[PATCH 0/2] am: support --always option to am empty commits

STALE1715d

97 messages, 11 authors, 2021-12-11 · page 1 of 2 · open the first message on its own page

[PATCH 0/2] am: support --always option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 05:00:46

Since that git has supported the --always option for the git-format-patch
command to create a patch with empty commit message, git-am should support
applying and committing with empty patches.

Aleen (2):
  doc: git-format-patch: specify the option --always
  am: support --always option to am empty commits

 Documentation/git-am.txt           |  5 +++++
 Documentation/git-format-patch.txt |  5 +++++
 builtin/am.c                       | 18 ++++++++++++++++--
 t/t4150-am.sh                      | 25 +++++++++++++++++++++++++
 4 files changed, 51 insertions(+), 2 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1076
-- 
gitgitgadget

[PATCH 1/2] doc: git-format-patch: specify the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 05:00:46

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 5 +++++
 1 file changed, 5 insertions(+)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..a9f2bf94182 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -30,6 +30,7 @@ SYNOPSIS
 		   [--range-diff=<previous> [--creation-factor=<percent>]]
 		   [--filename-max-length=<n>]
 		   [--progress]
+		   [--always]
 		   [<common diff options>]
 		   [ <since> | <revision range> ]
 
@@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --progress::
 	Show progress reports on stderr as patches are generated.
 
+--always::
+	Patch commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-diff-tree[1])
+
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
-- 
gitgitgadget

[PATCH 2/2] am: support --always option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 05:00:46

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  5 +++++
 builtin/am.c             | 18 ++++++++++++++++--
 t/t4150-am.sh            | 25 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..de5d11e404c 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--always]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -159,6 +160,10 @@ default.   You can use `--no-utf8` to override this.
 	countermand both `commit.gpgSign` configuration variable, and
 	earlier `--gpg-sign`.
 
+--always::
+	Apply patches of commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-format-patch[1])
+
 --continue::
 -r::
 --resolved::
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..d11efc16f92 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,6 +124,8 @@ struct am_state {
 	int ignore_date;
 	int allow_rerere_autoupdate;
 	const char *sign_commit;
+	int always;
+	int empty_commit;
 	int rebasing;
 };
 
@@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)
 	}
 
 	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
+		if (state->always) {
+			state->empty_commit = 1;
+		} else {
+			printf_ln(_("Patch is empty."));
+			die_user_resolve(state);
+		}
 	}
 
 	strbuf_addstr(&msg, "\n\n");
@@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (state->empty_commit)
+			goto commit;
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1836,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2367,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_BOOL(0, "always", &state.always,
+			N_("always apply patch event if the patch is empty")),
+		OPT_HIDDEN_BOOL(0, "empty-commit", &state.empty_commit,
+			N_("(internal use for skipping git-apply to empty commits)")),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..5b3617857a8 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'am a real empty patch with the --always option' '
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
+	test_must_fail git am --always empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'am a patch with empty commits' '
+	grep "empty commit" empty-commit.patch &&
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
+	git checkout empty-commit^ &&
+	git am --always empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git cat-file commit HEAD >actual &&
+	test_i18ngrep "empty commit" actual
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH 0/2] am: support --always option to am empty commits

From: René Scharfe <hidden>
Date: 2021-11-12 06:17:56

Am 12.11.21 um 05:58 schrieb Aleen via GitGitGadget:
Since that git has supported the --always option for the git-format-patch
command to create a patch with empty commit message, git-am should support
applying and committing with empty patches.
The symmetry is compelling, but "always" is quite generic.  I can see
e.g. someone expecting "git am --always" to imply --keep-non-patch.

git commit and cherry-pick have --allow-empty, which is (a bit) more
specific.  That seems to me a better option name to copy for a commit-
creating command like git am.

René

[PATCH v2 0/4] am: support --allow-empty option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 06:54:08

Since that git has supported the --always option for the git-format-patch
command to create a patch with empty commit message, git-am should support
applying and committing with empty patches.

Changes since v1:

 * test: am: add the case when not passing the --always option
 * chore: am: rename the --always option to --allow-empty

Aleen (4):
  doc: git-format-patch: specify the option --always
  am: support --always option to am empty commits
  test: am: add the case when not passing the --always option
  chore: am: rename the --always option to --allow-empty

 Documentation/git-am.txt           |  5 +++++
 Documentation/git-format-patch.txt |  5 +++++
 builtin/am.c                       | 18 +++++++++++++--
 t/t4150-am.sh                      | 35 ++++++++++++++++++++++++++++++
 4 files changed, 61 insertions(+), 2 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v1:

 1:  71e6989375c = 1:  71e6989375c doc: git-format-patch: specify the option --always
 2:  59b1417da37 = 2:  59b1417da37 am: support --always option to am empty commits
 -:  ----------- > 3:  da024ced668 test: am: add the case when not passing the --always option
 -:  ----------- > 4:  45e9720f40b chore: am: rename the --always option to --allow-empty

-- 
gitgitgadget

[PATCH v2 1/4] doc: git-format-patch: specify the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 06:54:10

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 5 +++++
 1 file changed, 5 insertions(+)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..a9f2bf94182 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -30,6 +30,7 @@ SYNOPSIS
 		   [--range-diff=<previous> [--creation-factor=<percent>]]
 		   [--filename-max-length=<n>]
 		   [--progress]
+		   [--always]
 		   [<common diff options>]
 		   [ <since> | <revision range> ]
 
@@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --progress::
 	Show progress reports on stderr as patches are generated.
 
+--always::
+	Patch commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-diff-tree[1])
+
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
-- 
gitgitgadget

[PATCH v2 2/4] am: support --always option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 06:54:12

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  5 +++++
 builtin/am.c             | 18 ++++++++++++++++--
 t/t4150-am.sh            | 25 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..de5d11e404c 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--always]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -159,6 +160,10 @@ default.   You can use `--no-utf8` to override this.
 	countermand both `commit.gpgSign` configuration variable, and
 	earlier `--gpg-sign`.
 
+--always::
+	Apply patches of commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-format-patch[1])
+
 --continue::
 -r::
 --resolved::
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..d11efc16f92 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,6 +124,8 @@ struct am_state {
 	int ignore_date;
 	int allow_rerere_autoupdate;
 	const char *sign_commit;
+	int always;
+	int empty_commit;
 	int rebasing;
 };
 
@@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)
 	}
 
 	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
+		if (state->always) {
+			state->empty_commit = 1;
+		} else {
+			printf_ln(_("Patch is empty."));
+			die_user_resolve(state);
+		}
 	}
 
 	strbuf_addstr(&msg, "\n\n");
@@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (state->empty_commit)
+			goto commit;
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1836,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2367,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_BOOL(0, "always", &state.always,
+			N_("always apply patch event if the patch is empty")),
+		OPT_HIDDEN_BOOL(0, "empty-commit", &state.empty_commit,
+			N_("(internal use for skipping git-apply to empty commits)")),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..5b3617857a8 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'am a real empty patch with the --always option' '
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
+	test_must_fail git am --always empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'am a patch with empty commits' '
+	grep "empty commit" empty-commit.patch &&
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
+	git checkout empty-commit^ &&
+	git am --always empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git cat-file commit HEAD >actual &&
+	test_i18ngrep "empty commit" actual
+'
+
 test_done
-- 
gitgitgadget

[PATCH v2 3/4] test: am: add the case when not passing the --always option

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 06:54:13

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 t/t4150-am.sh | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 5b3617857a8..364c61ba198 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1166,7 +1166,17 @@ test_expect_success 'am a real empty patch with the --always option' '
 	test_cmp expected actual
 '
 
-test_expect_success 'am a patch with empty commits' '
+test_expect_success 'am a patch of empty commits without the --always option' '
+	grep "empty commit" empty-commit.patch &&
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err
+'
+
+test_expect_success 'am a patch of empty commits with the --always option' '
 	grep "empty commit" empty-commit.patch &&
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
-- 
gitgitgadget

[PATCH v2 4/4] chore: am: rename the --always option to --allow-empty

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-12 06:54:15

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  4 ++--
 builtin/am.c             |  8 ++++----
 t/t4150-am.sh            | 10 +++++-----
 3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index de5d11e404c..6ed844af9c6 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
-	 [--always]
+	 [--allow-empty]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -160,7 +160,7 @@ default.   You can use `--no-utf8` to override this.
 	countermand both `commit.gpgSign` configuration variable, and
 	earlier `--gpg-sign`.
 
---always::
+--allow-empty::
 	Apply patches of commits with detailed commit messages,
 	even if they emit no changes. (see linkgit:git-format-patch[1])
 
diff --git a/builtin/am.c b/builtin/am.c
index d11efc16f92..a8661098acf 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,7 +124,7 @@ struct am_state {
 	int ignore_date;
 	int allow_rerere_autoupdate;
 	const char *sign_commit;
-	int always;
+	int allow_empty;
 	int empty_commit;
 	int rebasing;
 };
@@ -1251,7 +1251,7 @@ static int parse_mail(struct am_state *state, const char *mail)
 	}
 
 	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		if (state->always) {
+		if (state->allow_empty) {
 			state->empty_commit = 1;
 		} else {
 			printf_ln(_("Patch is empty."));
@@ -2367,8 +2367,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
-		OPT_BOOL(0, "always", &state.always,
-			N_("always apply patch event if the patch is empty")),
+		OPT_BOOL(0, "allow-empty", &state.allow_empty,
+			N_("allow to apply patches of empty commits")),
 		OPT_HIDDEN_BOOL(0, "empty-commit", &state.empty_commit,
 			N_("(internal use for skipping git-apply to empty commits)")),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 364c61ba198..b47a0fa41e7 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1158,15 +1158,15 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
-test_expect_success 'am a real empty patch with the --always option' '
+test_expect_success 'am a real empty patch with the --allow-empty option' '
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
-	test_must_fail git am --always empty.patch 2>actual &&
+	test_must_fail git am --allow-empty empty.patch 2>actual &&
 	echo Patch format detection failed. >expected &&
 	test_cmp expected actual
 '
 
-test_expect_success 'am a patch of empty commits without the --always option' '
+test_expect_success 'am a patch of empty commits without the --allow-empty option' '
 	grep "empty commit" empty-commit.patch &&
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
@@ -1176,12 +1176,12 @@ test_expect_success 'am a patch of empty commits without the --always option' '
 	test_i18ngrep "Patch is empty." err
 '
 
-test_expect_success 'am a patch of empty commits with the --always option' '
+test_expect_success 'am a patch of empty commits with the --allow-empty option' '
 	grep "empty commit" empty-commit.patch &&
 	rm -fr .git/rebase-apply &&
 	git reset --hard &&
 	git checkout empty-commit^ &&
-	git am --always empty-commit.patch &&
+	git am --allow-empty empty-commit.patch &&
 	test_path_is_missing .git/rebase-apply &&
 	git cat-file commit HEAD >actual &&
 	test_i18ngrep "empty commit" actual
-- 
gitgitgadget

RE: [PATCH 0/2] am: support --always option to am empty commits

From: Aleen <hidden>
Date: 2021-11-12 06:57:18

Dears René,
The symmetry is compelling, but "always" is quite generic.  I can see
e.g. someone expecting "git am --always" to imply --keep-non-patch.
git commit and cherry-pick have --allow-empty, which is (a bit) more
specific.  That seems to me a better option name to copy for a commit-
creating command like git am.
It was designed corresponding to --always option in git-format-patch, which
will be pssed into git-diff-tree. As a commit-creating command, --allow-empty
is apparently a better choice. I will re-submit it, thank you.

Re: [PATCH v2 1/4] doc: git-format-patch: specify the option --always

From: Junio C Hamano <hidden>
Date: 2021-11-12 22:17:34

"Aleen via GitGitGadget" [off-list ref] writes:
From: Aleen <redacted>
Subject: Re: [PATCH v2 1/4] doc: git-format-patch: specify the option --always
"specify" -> "describe", perhaps?
quoted hunk
Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 5 +++++
 1 file changed, 5 insertions(+)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..a9f2bf94182 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -30,6 +30,7 @@ SYNOPSIS
 		   [--range-diff=<previous> [--creation-factor=<percent>]]
 		   [--filename-max-length=<n>]
 		   [--progress]
+		   [--always]
 		   [<common diff options>]
 		   [ <since> | <revision range> ]
 
@@ -388,6 +389,10 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --progress::
 	Show progress reports on stderr as patches are generated.
 
+--always::
+	Patch commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-diff-tree[1])
What does the verb "Patch" mean here?  It cannot be what the command
"patch" does, i.e. apply a diff to working tree files, as
format-patch does not apply any patch.  

Who guarantees that commit messages are detailed?  If I write a
commit that does not change anything with a one-liner message, would
this option make that message more detailed?  I doubt that it would
be the case.

This option may sit much better near the --ignore-if-in-upstream
option.  The primary way the command is told which commits to show
is via its argument (<since> or <revision-range>), and we do not
have many options to affect the selection of commits, but this one
and --ignore-if-in-upstream are such options.  Borrowing from the
way the description of that other option is phrased, perhaps

    Include patches for commits that do not introduce any change,
    which are omitted by default.

Do not refer to 'git-diff-tree'; I do not think the reader who is
learning how to drive format-patch will learn anything new by
reading that page.

I have a feeling that if we were to endorse and promote this option
by documenting (note: it was an accident and a mistake that this
option is understood by underlying revision.c parse machinery, and
not a designed behaviour for format-patch to do anything to empty
commits), we should give users a better synonym to invoke it, but
that can and should be outside of this step.

Re: [PATCH v2 2/4] am: support --always option to am empty commits

From: Junio C Hamano <hidden>
Date: 2021-11-12 22:23:05

"Aleen via GitGitGadget" [off-list ref] writes:
From: Aleen <redacted>
Subject: Re: [PATCH v2 2/4] am: support --always option to am empty commits
As the inventor of "format-patch" and "am", I probably should wish
that "to am" were by now a valid verb, but no, it is not.

More importantly, when an empty patch comes, there can be many
different ways for the "am" command to handle it.  "to am empty
commits" does not say how the patch chooses to so and does not make
a very useful title for this commit.

Right now, we error out, simply because it is an easy mistake to
save a non-patch e-mail to the mailbox when intending to save a
series of patches belonging to a topic, and the user is expected to
say "git am --skip" to skip over it when it happens.  The above
"Subject:" can be read to mean that the new option instead allows
such an empty message to be skipped without stopping and forcing the
user to say "am --skip", which may be a useful thing to do.  Or it
may mean that the new option creates an empty commit, using the
contents of the e-mail as the commit log message.  Does this patch
offer both behaviour?  If so, "to am", even though it does not
convey a bit of information, might be an acceptable compromise.  If
the patch implements only one of the behaviours, then we should say
so.  Either one of these two:

    am: --always option skips empty patches
    am: --always option records empty patches as empty commits

Also, I thought that the previous round saw a conclusion that --always
is a bad name for the option.  If we are making the second round,
let's not start with a bad name and the "fix the mistake" of
starting with a bad name in a later step.  Just start with the final
name from the beginning.
+--always::
+	Apply patches of commits with detailed commit messages,
+	even if they emit no changes. (see linkgit:git-format-patch[1])
Almost the same comment as 1/4 applies to the above description.

    --empty-patch=(skip|asis|die)::
	The command usually errors out when seeing an input e-mail
	message that lacks a patch.  When this option is set to
	'skip', skip such an e-mail message without erroring out.
	When this option is set to 'asis', create an empty commit,
	recording the contents of the e-mail message as its log.
	'die' is the default.


perhaps?  Assuming that 'skip' would make a useful addition to the
mix in the future.
quoted hunk
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..d11efc16f92 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -124,6 +124,8 @@ struct am_state {
 	int ignore_date;
 	int allow_rerere_autoupdate;
 	const char *sign_commit;
+	int always;
OK, so here is where the parse_options() records the command line
option.
+	int empty_commit;
I do not think this addtion of empty_commit member to this structure
is welcome or necessary.
 	int rebasing;
 };
quoted hunk
@@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)
 	}
 	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
+		if (state->always) {
+			state->empty_commit = 1;
+		} else {
+			printf_ln(_("Patch is empty."));
+			die_user_resolve(state);
+		}
 	}
I am only thinking aloud, but I suspect that the whole "if 'patch'
is empty, do something special" code logically belongs to the
caller.  Perhaps we should remove this block altogether and let the
code continue the rest of this function.  And return 0, as this is
not like mail-system-internal-data that we want to pretend did not
even exist, and have the caller check if "patch" file is empty and
act accordingly.
quoted hunk
@@ -1792,6 +1798,9 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (state->empty_commit)
+			goto commit;
+
This is probably a wrong place to jump from.  You are bypassing
applypatch-msg-hook that may be serving as a gate to catch typos
if you are going to create a commit.

So, perhaps check if "patch" is empty here, using the code you'd
lift from parse_mail(), and if it is empty then:

  - if --empty-commit is set to die (or left default), do the
    printf_ln(_("Patch is empty.")) followed by a call to
    die_user_resolve(state), just like before.

  - if it is set to skip, jump to "next", just like when
    parse_mail() returned 1.

  - otherwise (i.e. you are told to create an empty commit),
    remember the fact that current e-mail has no patch, but continue
    to the next step to run the hook.
 		if (run_applypatch_msg_hook(state))
 			exit(1);
And after passing the hook, if your earlier check says that there is
no patch and you are to create an empty commit, jump to "commit"
label from here.
quoted hunk
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..5b3617857a8 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,23 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'am a real empty patch with the --always option' '
+	rm -fr .git/rebase-apply &&
What is this one about?  If this is trying to clean up the cruft the
previous step made, it may be better to do the clean-up in the
previous step using test_when_finished.
+	git reset --hard &&
+	test_must_fail git am --always empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
It is curious that the error message the patch touched said "Patch
is empty." but the test checks for a different message.  Are we
testing the right failure mode?
+test_expect_success 'am a patch with empty commits' '
+	grep "empty commit" empty-commit.patch &&
What is this testing?  If it is checking the sanity of test data we
created earlier, shouldn't we do so where we generated the data
(i.e. the "setup" block that we earlier saw)?
+	rm -fr .git/rebase-apply &&
+	git reset --hard &&
These are trying to clean up the cruft the previous step (added by
this patch) may have left.  Perhaps these should be done inside
test_when_finished of the previous step?
+	git checkout empty-commit^ &&
+	git am --always empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
We should trust "git am"'s exit status here, I would think, rather
than be so intimate with the internal implementation detail like the
name of the temporary directory the command uses.
+	git cat-file commit HEAD >actual &&
+	test_i18ngrep "empty commit" actual
test_i18ngrep -> grep

The input (i.e. the commit that resulted in this empty patch) said.
"empty commit", and we are making sure that string appears, but we
are not making sure that is the only string appears in the log
message.  Is it because we will later enhance the command to
automatically extend the single-liner "empty patch" log message into
a lot more detailed one?  I doubt it ;-)

More importantly, the above checks if (part of) the log message is
recorded, but does not check if the resulting commit is what is
expected, i.e. an empty one.

Perhaps checking with "grep" is way too loose a test.  Shouldn't we
do something like

	git show -1 --format='%B' >actual

and compare it with expected "the log is recorded as-is, and there
is no change between HEAD^ and HEAD"?
+'
+
 test_done
Thanks.

[PATCH v3 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-15 10:39:36

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option
 2. rename the --always option to --allow-empty

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty-commit option to handle empty patches

 Documentation/git-am.txt           |  9 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 48 ++++++++++++++++++++++---
 t/t4150-am.sh                      | 58 ++++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+), 6 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v2:

 1:  71e6989375c ! 1:  9f1b3dd6d0b doc: git-format-patch: specify the option --always
     @@ Metadata
      Author: Aleen [off-list ref]
      
       ## Commit message ##
     -    doc: git-format-patch: specify the option --always
     +    doc: git-format-patch: describe the option --always
      
          Signed-off-by: Aleen [off-list ref]
      
       ## Documentation/git-format-patch.txt ##
      @@ Documentation/git-format-patch.txt: SYNOPSIS
     - 		   [--range-diff=<previous> [--creation-factor=<percent>]]
     - 		   [--filename-max-length=<n>]
     - 		   [--progress]
     -+		   [--always]
     - 		   [<common diff options>]
     - 		   [ <since> | <revision range> ]
     - 
     -@@ Documentation/git-format-patch.txt: you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
     - --progress::
     - 	Show progress reports on stderr as patches are generated.
     + 		   [-n | --numbered | -N | --no-numbered]
     + 		   [--start-number <n>] [--numbered-files]
     + 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
     +-		   [--ignore-if-in-upstream]
     ++		   [--ignore-if-in-upstream] [--always]
     + 		   [--cover-from-description=<mode>]
     + 		   [--rfc] [--subject-prefix=<subject prefix>]
     + 		   [(--reroll-count|-v) <n>]
     +@@ Documentation/git-format-patch.txt: will want to ensure that threading is disabled for `git send-email`.
     + 	patches being generated, and any patch that matches is
     + 	ignored.
       
      +--always::
     -+	Patch commits with detailed commit messages,
     -+	even if they emit no changes. (see linkgit:git-diff-tree[1])
     ++	Include patches for commits that do not introduce any change,
     ++	which are omitted by default.
      +
     - CONFIGURATION
     - -------------
     - You can specify extra mail header lines to be added to each message,
     + --cover-from-description=<mode>::
     + 	Controls which parts of the cover letter will be automatically
     + 	populated using the branch's description.
 2:  59b1417da37 ! 2:  ef33ce8c6f9 am: support --always option to am empty commits
     @@ Metadata
      Author: Aleen [off-list ref]
      
       ## Commit message ##
     -    am: support --always option to am empty commits
     +    am: support --empty-commit option to handle empty patches
      
          Signed-off-by: Aleen [off-list ref]
      
     @@ Documentation/git-am.txt: SYNOPSIS
       	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
       	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
       	 [--quoted-cr=<action>]
     -+	 [--always]
     ++	 [--empty-commit=(die|skip|asis)]
       	 [(<mbox> | <Maildir>)...]
       'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
       
     -@@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.
     - 	countermand both `commit.gpgSign` configuration variable, and
     - 	earlier `--gpg-sign`.
     +@@ Documentation/git-am.txt: OPTIONS
     + --quoted-cr=<action>::
     + 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
       
     -+--always::
     -+	Apply patches of commits with detailed commit messages,
     -+	even if they emit no changes. (see linkgit:git-format-patch[1])
     ++--empty-commit=(die|skip|asis)::
     ++	The command usually errors out when seeing an input e-mail
     ++	message that lacks a patch. When this option is set to
     ++	'skip', skip such an e-mail message without outputting error.
     ++	When this option is set to 'asis', create an empty commit,
     ++	recording the contents of the e-mail message as its log.
     ++	'die' is specified by default.
      +
     - --continue::
     - -r::
     - --resolved::
     + -m::
     + --message-id::
     + 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
      
       ## builtin/am.c ##
     -@@ builtin/am.c: struct am_state {
     - 	int ignore_date;
     - 	int allow_rerere_autoupdate;
     - 	const char *sign_commit;
     -+	int always;
     -+	int empty_commit;
     - 	int rebasing;
     +@@ builtin/am.c: enum show_patch_type {
     + 	SHOW_PATCH_DIFF = 1,
       };
       
     ++enum empty_commit_action {
     ++	DIE_EMPTY_COMMIT = 0,  /* output errors */
     ++	SKIP_EMPTY_COMMIT,     /* skip without outputting errors */
     ++	ASIS_EMPTY_COMMIT      /* keep recording as empty commits */
     ++};
     ++
     + struct am_state {
     + 	/* state directory path */
     + 	char *dir;
     +@@ builtin/am.c: struct am_state {
     + 	int message_id;
     + 	int scissors; /* enum scissors_type */
     + 	int quoted_cr; /* enum quoted_cr_action */
     ++	int empty_commit; /* enum empty_commit_action */
     + 	struct strvec git_apply_opts;
     + 	const char *resolvemsg;
     + 	int committer_date_is_author_date;
     +@@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,
     + 	return 0;
     + }
     + 
     ++static int am_option_parse_empty_commit(const struct option *opt,
     ++				     const char *arg, int unset)
     ++{
     ++	int *opt_value = opt->value;
     ++
     ++	if (unset || !strcmp(arg, "die"))
     ++		*opt_value = DIE_EMPTY_COMMIT;
     ++	else if (!strcmp(arg, "skip"))
     ++		*opt_value = SKIP_EMPTY_COMMIT;
     ++	else if (!strcmp(arg, "asis"))
     ++		*opt_value = ASIS_EMPTY_COMMIT;
     ++	else
     ++		return error(_("Invalid value for --empty-commit: %s"), arg);
     ++
     ++	return 0;
     ++}
     ++
     + /**
     +  * Returns path relative to the am_state directory.
     +  */
      @@ builtin/am.c: static int parse_mail(struct am_state *state, const char *mail)
     + 		goto finish;
       	}
       
     - 	if (is_empty_or_missing_file(am_path(state, "patch"))) {
     +-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
      -		printf_ln(_("Patch is empty."));
      -		die_user_resolve(state);
     -+		if (state->always) {
     -+			state->empty_commit = 1;
     -+		} else {
     -+			printf_ln(_("Patch is empty."));
     -+			die_user_resolve(state);
     -+		}
     - 	}
     - 
     +-	}
     +-
       	strbuf_addstr(&msg, "\n\n");
     + 	strbuf_addbuf(&msg, &mi.log_message);
     + 	strbuf_stripspace(&msg, 0);
      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
       		if (state->interactive && do_interactive(state))
       			goto next;
       
     -+		if (state->empty_commit)
     -+			goto commit;
     ++		if (is_empty_or_missing_file(am_path(state, "patch"))) {
     ++			if (state->empty_commit == SKIP_EMPTY_COMMIT)
     ++				goto next;
     ++			else if (state->empty_commit == ASIS_EMPTY_COMMIT) {
     ++				if (run_applypatch_msg_hook(state))
     ++					exit(1);
     ++				else
     ++					goto commit;
     ++			} else if (state->empty_commit == DIE_EMPTY_COMMIT) {
     ++				printf_ln(_("Patch is empty."));
     ++				die_user_resolve(state);
     ++			}
     ++		}
      +
       		if (run_applypatch_msg_hook(state))
       			exit(1);
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
       		  N_("GPG-sign commits"),
       		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
     -+		OPT_BOOL(0, "always", &state.always,
     -+			N_("always apply patch event if the patch is empty")),
     -+		OPT_HIDDEN_BOOL(0, "empty-commit", &state.empty_commit,
     -+			N_("(internal use for skipping git-apply to empty commits)")),
     ++		{ OPTION_CALLBACK, 0, "empty-commit", &state.empty_commit,
     ++		  "(die|skip|asis)",
     ++		  N_("specify how to handle empty patches"),
     ++		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
       		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
       			N_("(internal use for git-rebase)")),
       		OPT_END()
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
       	git -C client am ../patch
       '
       
     -+test_expect_success 'am a real empty patch with the --always option' '
     -+	rm -fr .git/rebase-apply &&
     -+	git reset --hard &&
     -+	test_must_fail git am --always empty.patch 2>actual &&
     ++test_expect_success 'still output error with --empty-commit when meeting empty files' '
     ++	test_must_fail git am --empty-commit=skip empty.patch 2>actual &&
      +	echo Patch format detection failed. >expected &&
      +	test_cmp expected actual
      +'
      +
     -+test_expect_success 'am a patch with empty commits' '
     -+	grep "empty commit" empty-commit.patch &&
     -+	rm -fr .git/rebase-apply &&
     -+	git reset --hard &&
     ++test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
      +	git checkout empty-commit^ &&
     -+	git am --always empty-commit.patch &&
     ++	test_must_fail git am empty-commit.patch >err &&
     ++	test_path_is_dir .git/rebase-apply &&
     ++	test_i18ngrep "Patch is empty." err &&
     ++	rm -fr .git/rebase-apply &&
     ++
     ++	test_must_fail git am --empty-commit=die empty-commit.patch >err &&
     ++	test_path_is_dir .git/rebase-apply &&
     ++	test_i18ngrep "Patch is empty." err &&
     ++	rm -fr .git/rebase-apply &&
     ++
     ++	test_must_fail git am --empty-commit=die cover-letter.patch >err &&
     ++	test_path_is_dir .git/rebase-apply &&
     ++	test_i18ngrep "Patch is empty." err &&
     ++	rm -fr .git/rebase-apply
     ++'
     ++
     ++test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
     ++	git am --empty-commit=skip empty-commit.patch >err &&
     ++	test_path_is_missing .git/rebase-apply &&
     ++	git rev-parse empty-commit^ >expected &&
     ++	git rev-parse HEAD >actual &&
     ++	test_cmp expected actual &&
     ++
     ++	git am --empty-commit=skip cover-letter.patch >err &&
     ++	test_path_is_missing .git/rebase-apply &&
     ++	git rev-parse empty-commit^ >expected &&
     ++	git rev-parse HEAD >actual &&
     ++	test_cmp expected actual
     ++'
     ++
     ++test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
     ++	git am --empty-commit=asis empty-commit.patch &&
     ++	test_path_is_missing .git/rebase-apply &&
     ++	git show empty-commit --format="%B" >expected &&
     ++	git show HEAD --format="%B" >actual &&
     ++	grep -f actual expected &&
     ++
     ++	git am --empty-commit=asis cover-letter.patch &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git cat-file commit HEAD >actual &&
     -+	test_i18ngrep "empty commit" actual
     ++	git show empty-commit --format="%B" >expected &&
     ++	git show HEAD --format="%B" >actual &&
     ++	grep -f actual expected
      +'
      +
       test_done
 3:  da024ced668 < -:  ----------- test: am: add the case when not passing the --always option
 4:  45e9720f40b < -:  ----------- chore: am: rename the --always option to --allow-empty

-- 
gitgitgadget

[PATCH v3 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-15 10:39:47

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v3 2/2] am: support --empty-commit option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-15 10:39:51

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 48 +++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 58 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 110 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..d8d3bf202d7 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty-commit=(die|skip|asis)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty-commit=(die|skip|asis)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'skip', skip such an e-mail message without outputting error.
+	When this option is set to 'asis', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..e7755c1377e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_commit_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	SKIP_EMPTY_COMMIT,     /* skip without outputting errors */
+	ASIS_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_commit; /* enum empty_commit_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty_commit(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (unset || !strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "skip"))
+		*opt_value = SKIP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "asis"))
+		*opt_value = ASIS_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty-commit: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			if (state->empty_commit == SKIP_EMPTY_COMMIT)
+				goto next;
+			else if (state->empty_commit == ASIS_EMPTY_COMMIT) {
+				if (run_applypatch_msg_hook(state))
+					exit(1);
+				else
+					goto commit;
+			} else if (state->empty_commit == DIE_EMPTY_COMMIT) {
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		{ OPTION_CALLBACK, 0, "empty-commit", &state.empty_commit,
+		  "(die|skip|asis)",
+		  N_("specify how to handle empty patches"),
+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..13fdf2f86c6 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,56 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'still output error with --empty-commit when meeting empty files' '
+	test_must_fail git am --empty-commit=skip empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die cover-letter.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=skip empty-commit.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+
+	git am --empty-commit=skip cover-letter.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=asis empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected &&
+
+	git am --empty-commit=asis cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v3 2/2] am: support --empty-commit option to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-15 11:14:52

Dears Hamano,

Sorry about the late submission, and I have modified it to support the `--empty-commit=(die|skip|asis)` pattern.
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=asis empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected &&
+
+	git am --empty-commit=asis cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected
+'
When it comes to the last test case, there are some accidental contents recognized as commit messages inside an empty patch, like:

1. version signatures:
  > -- 
  > 2.34.0.rc2.390.gef33ce8c6f
2. information of cover letter:
  > *** BLURB HERE ***
  >
  > A U Thor (1):
  >   empty commit

I 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?

Aleen

[PATCH v4 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-16 05:54:04

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option
 2. rename the --always option to --allow-empty

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases
 2. grep -f cannot be used under Mac OS

----------------------------------------------------------------------------

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty-commit option to handle empty patches

 Documentation/git-am.txt           |  9 ++++
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 48 ++++++++++++++++++--
 t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++++++
 4 files changed, 130 insertions(+), 6 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v3:

 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always
 2:  ef33ce8c6f9 ! 2:  b7e30c9b7ab am: support --empty-commit option to handle empty patches
     @@ t/t4150-am.sh: test_expect_success setup '
      +	git commit -m "empty commit" --allow-empty &&
      +
      +	git format-patch --stdout empty-commit^ >empty.patch &&
     ++	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
      +	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
      +
       	# reset time
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +
      +	git am --empty-commit=skip cover-letter.patch >err &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git rev-parse empty-commit^ >expected &&
     -+	git rev-parse HEAD >actual &&
     -+	test_cmp expected actual
     ++	test_cmp_rev empty-commit^ HEAD
      +'
      +
      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
      +	git am --empty-commit=asis empty-commit.patch &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git show empty-commit --format="%B" >expected &&
     ++	{
     ++		git show empty-commit --format="%B" &&
     ++		echo "--" &&
     ++		git version | sed -e "s/^git version //" &&
     ++		echo
     ++	} >expected &&
      +	git show HEAD --format="%B" >actual &&
     -+	grep -f actual expected &&
     ++	test_cmp actual expected &&
      +
      +	git am --empty-commit=asis cover-letter.patch &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git show empty-commit --format="%B" >expected &&
     ++	{
     ++		echo "*** SUBJECT HERE ***" &&
     ++		echo &&
     ++		echo "*** BLURB HERE ***" &&
     ++		echo &&
     ++		echo "A U Thor (1):" &&
     ++		printf "  " &&
     ++		git show empty-commit --format="%B" &&
     ++		echo "--" &&
     ++		git version | sed -e "s/^git version //" &&
     ++		echo
     ++	} >expected &&
      +	git show HEAD --format="%B" >actual &&
     -+	grep -f actual expected
     ++	test_cmp actual expected
      +'
      +
       test_done

-- 
gitgitgadget

[PATCH v4 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-16 05:54:18

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v4 2/2] am: support --empty-commit option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-16 05:54:21

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..d8d3bf202d7 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty-commit=(die|skip|asis)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty-commit=(die|skip|asis)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'skip', skip such an e-mail message without outputting error.
+	When this option is set to 'asis', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..e7755c1377e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_commit_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	SKIP_EMPTY_COMMIT,     /* skip without outputting errors */
+	ASIS_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_commit; /* enum empty_commit_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty_commit(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (unset || !strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "skip"))
+		*opt_value = SKIP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "asis"))
+		*opt_value = ASIS_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty-commit: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			if (state->empty_commit == SKIP_EMPTY_COMMIT)
+				goto next;
+			else if (state->empty_commit == ASIS_EMPTY_COMMIT) {
+				if (run_applypatch_msg_hook(state))
+					exit(1);
+				else
+					goto commit;
+			} else if (state->empty_commit == DIE_EMPTY_COMMIT) {
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		{ OPTION_CALLBACK, 0, "empty-commit", &state.empty_commit,
+		  "(die|skip|asis)",
+		  N_("specify how to handle empty patches"),
+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..e657180c201 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,13 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'still output error with --empty-commit when meeting empty files' '
+	test_must_fail git am --empty-commit=skip empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die cover-letter.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=skip empty-commit.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+
+	git am --empty-commit=skip cover-letter.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	test_cmp_rev empty-commit^ HEAD
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=asis empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected &&
+
+	git am --empty-commit=asis cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		echo "*** SUBJECT HERE ***" &&
+		echo &&
+		echo "*** BLURB HERE ***" &&
+		echo &&
+		echo "A U Thor (1):" &&
+		printf "  " &&
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches

From: Phillip Wood <hidden>
Date: 2021-11-16 10:07:54

Hi Aleen

Thanks for working on this

On 16/11/2021 05:18, Aleen via GitGitGadget wrote:
quoted hunk
From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
  Documentation/git-am.txt |  9 +++++
  builtin/am.c             | 48 +++++++++++++++++++++++---
  t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
  3 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..d8d3bf202d7 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
  	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
  	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
  	 [--quoted-cr=<action>]
+	 [--empty-commit=(die|skip|asis)]
  	 [(<mbox> | <Maildir>)...]
  'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
  
@@ -63,6 +64,14 @@ OPTIONS
  --quoted-cr=<action>::
  	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
  
+--empty-commit=(die|skip|asis)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'skip', skip such an e-mail message without outputting error.
+	When this option is set to 'asis', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
This feels sufficiently similar to the case of handling empty commits in 
'git rebase' that it is worth trying to have a similar user interface. 
Otherwise the two commands have two different option names doing more or 
less the same thing. 'git rebase' has --empty=[drop,keep,ask] where drop 
is the default. If am were to accept --empty=[drop,keep,die] it would 
offer a similar user experience.

Best Wishes

Phillip
quoted hunk
  -m::
  --message-id::
  	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..e7755c1377e 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
  	SHOW_PATCH_DIFF = 1,
  };
  
+enum empty_commit_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	SKIP_EMPTY_COMMIT,     /* skip without outputting errors */
+	ASIS_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
  struct am_state {
  	/* state directory path */
  	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
  	int message_id;
  	int scissors; /* enum scissors_type */
  	int quoted_cr; /* enum quoted_cr_action */
+	int empty_commit; /* enum empty_commit_action */
  	struct strvec git_apply_opts;
  	const char *resolvemsg;
  	int committer_date_is_author_date;
@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,
  	return 0;
  }
  
+static int am_option_parse_empty_commit(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (unset || !strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "skip"))
+		*opt_value = SKIP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "asis"))
+		*opt_value = ASIS_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty-commit: %s"), arg);
+
+	return 0;
+}
+
  /**
   * Returns path relative to the am_state directory.
   */
@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)
  		goto finish;
  	}
  
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
  	strbuf_addstr(&msg, "\n\n");
  	strbuf_addbuf(&msg, &mi.log_message);
  	strbuf_stripspace(&msg, 0);
@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)
  		if (state->interactive && do_interactive(state))
  			goto next;
  
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			if (state->empty_commit == SKIP_EMPTY_COMMIT)
+				goto next;
+			else if (state->empty_commit == ASIS_EMPTY_COMMIT) {
+				if (run_applypatch_msg_hook(state))
+					exit(1);
+				else
+					goto commit;
+			} else if (state->empty_commit == DIE_EMPTY_COMMIT) {
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+			}
+		}
+
  		if (run_applypatch_msg_hook(state))
  			exit(1);
  
@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)
  			die_user_resolve(state);
  		}
  
+commit:
  		do_commit(state);
  
  next:
@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
  		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
  		  N_("GPG-sign commits"),
  		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		{ OPTION_CALLBACK, 0, "empty-commit", &state.empty_commit,
+		  "(die|skip|asis)",
+		  N_("specify how to handle empty patches"),
+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
  		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
  			N_("(internal use for git-rebase)")),
  		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..e657180c201 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,13 @@ test_expect_success setup '
  
  	git format-patch -M --stdout lorem^ >rename-add.patch &&
  
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
  	# reset time
  	sane_unset test_tick &&
  	test_tick
@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '
  	git -C client am ../patch
  '
  
+test_expect_success 'still output error with --empty-commit when meeting empty files' '
+	test_must_fail git am --empty-commit=skip empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty-commit=die cover-letter.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=skip empty-commit.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+
+	git am --empty-commit=skip cover-letter.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	test_cmp_rev empty-commit^ HEAD
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty-commit=asis empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected &&
+
+	git am --empty-commit=asis cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		echo "*** SUBJECT HERE ***" &&
+		echo &&
+		echo "*** BLURB HERE ***" &&
+		echo &&
+		echo "A U Thor (1):" &&
+		printf "  " &&
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected
+'
+
  test_done

Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-16 10:33:23

quoted
+--empty-commit=(die|skip|asis)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'skip', skip such an e-mail message without outputting error.
+	When this option is set to 'asis', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
This feels sufficiently similar to the case of handling empty commits in 
'git rebase' that it is worth trying to have a similar user interface. 
Otherwise the two commands have two different option names doing more or 
less the same thing. 'git rebase' has --empty=[drop,keep,ask] where drop 
is the default. If am were to accept --empty=[drop,keep,die] it would 
offer a similar user experience.

Best Wishes

Phillip
Dears Phillip,

It seems a good idea. Can Hamano make a decision?

Aleen

Re: [PATCH v4 2/2] am: support --empty-commit option to handle empty patches

From: Junio C Hamano <hidden>
Date: 2021-11-17 08:39:14

Phillip Wood [off-list ref] writes:
quoted
  +--empty-commit=(die|skip|asis)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'skip', skip such an e-mail message without outputting error.
+	When this option is set to 'asis', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
This feels sufficiently similar to the case of handling empty commits
in 'git rebase' that it is worth trying to have a similar user
interface. Otherwise the two commands have two different option names
doing more or less the same thing. 'git rebase' has
--empty=[drop,keep,ask] where drop is the default. If am were to
 accept --empty=[drop,keep,die] it would offer a similar user
experience.
Ah, thanks for noticing.  I like the three words you suggest.  If we
already have a similar option, we definitely should follow suit, and
I think "--empty" would be a better fit than "--empty-commit" in the
context of talking about the _input_ to "am".

[PATCH v5 0/2] am: support --empty-commit=(die|skip|asis) option to am empty commits

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-17 09:34:01

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty option to handle empty patches

 Documentation/git-am.txt           |  9 ++++
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 48 ++++++++++++++++++--
 t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++++++
 4 files changed, 130 insertions(+), 6 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v4:

 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always
 2:  b7e30c9b7ab ! 2:  96d8573dc80 am: support --empty-commit option to handle empty patches
     @@ Metadata
      Author: Aleen [off-list ref]
      
       ## Commit message ##
     -    am: support --empty-commit option to handle empty patches
     +    am: support --empty option to handle empty patches
      
          Signed-off-by: Aleen [off-list ref]
      
     @@ Documentation/git-am.txt: SYNOPSIS
       	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
       	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
       	 [--quoted-cr=<action>]
     -+	 [--empty-commit=(die|skip|asis)]
     ++	 [--empty=(die|drop|keep)]
       	 [(<mbox> | <Maildir>)...]
       'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
       
     @@ Documentation/git-am.txt: OPTIONS
       --quoted-cr=<action>::
       	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
       
     -+--empty-commit=(die|skip|asis)::
     ++--empty-commit=(die|drop|keep)::
      +	The command usually errors out when seeing an input e-mail
      +	message that lacks a patch. When this option is set to
     -+	'skip', skip such an e-mail message without outputting error.
     -+	When this option is set to 'asis', create an empty commit,
     ++	'drop', skip such an e-mail message without outputting error.
     ++	When this option is set to 'keep', create an empty commit,
      +	recording the contents of the e-mail message as its log.
      +	'die' is specified by default.
      +
     @@ builtin/am.c: enum show_patch_type {
       	SHOW_PATCH_DIFF = 1,
       };
       
     -+enum empty_commit_action {
     ++enum empty_action {
      +	DIE_EMPTY_COMMIT = 0,  /* output errors */
     -+	SKIP_EMPTY_COMMIT,     /* skip without outputting errors */
     -+	ASIS_EMPTY_COMMIT      /* keep recording as empty commits */
     ++	DROP_EMPTY_COMMIT,     /* skip without outputting errors */
     ++	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
      +};
      +
       struct am_state {
     @@ builtin/am.c: struct am_state {
       	int message_id;
       	int scissors; /* enum scissors_type */
       	int quoted_cr; /* enum quoted_cr_action */
     -+	int empty_commit; /* enum empty_commit_action */
     ++	int empty_type; /* enum empty_action */
       	struct strvec git_apply_opts;
       	const char *resolvemsg;
       	int committer_date_is_author_date;
     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,
      +
      +	if (unset || !strcmp(arg, "die"))
      +		*opt_value = DIE_EMPTY_COMMIT;
     -+	else if (!strcmp(arg, "skip"))
     -+		*opt_value = SKIP_EMPTY_COMMIT;
     -+	else if (!strcmp(arg, "asis"))
     -+		*opt_value = ASIS_EMPTY_COMMIT;
     ++	else if (!strcmp(arg, "drop"))
     ++		*opt_value = DROP_EMPTY_COMMIT;
     ++	else if (!strcmp(arg, "keep"))
     ++		*opt_value = KEEP_EMPTY_COMMIT;
      +	else
     -+		return error(_("Invalid value for --empty-commit: %s"), arg);
     ++		return error(_("Invalid value for --empty: %s"), arg);
      +
      +	return 0;
      +}
     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
       			goto next;
       
      +		if (is_empty_or_missing_file(am_path(state, "patch"))) {
     -+			if (state->empty_commit == SKIP_EMPTY_COMMIT)
     ++			if (state->empty_type == DROP_EMPTY_COMMIT)
      +				goto next;
     -+			else if (state->empty_commit == ASIS_EMPTY_COMMIT) {
     ++			else if (state->empty_type == KEEP_EMPTY_COMMIT) {
      +				if (run_applypatch_msg_hook(state))
      +					exit(1);
      +				else
      +					goto commit;
     -+			} else if (state->empty_commit == DIE_EMPTY_COMMIT) {
     ++			} else if (state->empty_type == DIE_EMPTY_COMMIT) {
      +				printf_ln(_("Patch is empty."));
      +				die_user_resolve(state);
      +			}
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
       		  N_("GPG-sign commits"),
       		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
     -+		{ OPTION_CALLBACK, 0, "empty-commit", &state.empty_commit,
     -+		  "(die|skip|asis)",
     ++		{ OPTION_CALLBACK, 0, "empty", &state.empty_type,
     ++		  "(die|drop|keep)",
      +		  N_("specify how to handle empty patches"),
      +		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
       		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
       	git -C client am ../patch
       '
       
     -+test_expect_success 'still output error with --empty-commit when meeting empty files' '
     -+	test_must_fail git am --empty-commit=skip empty.patch 2>actual &&
     ++test_expect_success 'still output error with --empty when meeting empty files' '
     ++	test_must_fail git am --empty=drop empty.patch 2>actual &&
      +	echo Patch format detection failed. >expected &&
      +	test_cmp expected actual
      +'
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +	test_i18ngrep "Patch is empty." err &&
      +	rm -fr .git/rebase-apply &&
      +
     -+	test_must_fail git am --empty-commit=die empty-commit.patch >err &&
     ++	test_must_fail git am --empty=die empty-commit.patch >err &&
      +	test_path_is_dir .git/rebase-apply &&
      +	test_i18ngrep "Patch is empty." err &&
      +	rm -fr .git/rebase-apply &&
      +
     -+	test_must_fail git am --empty-commit=die cover-letter.patch >err &&
     ++	test_must_fail git am --empty=die cover-letter.patch >err &&
      +	test_path_is_dir .git/rebase-apply &&
      +	test_i18ngrep "Patch is empty." err &&
      +	rm -fr .git/rebase-apply
      +'
      +
      +test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
     -+	git am --empty-commit=skip empty-commit.patch >err &&
     ++	git am --empty=drop empty-commit.patch >err &&
      +	test_path_is_missing .git/rebase-apply &&
      +	git rev-parse empty-commit^ >expected &&
      +	git rev-parse HEAD >actual &&
      +	test_cmp expected actual &&
      +
     -+	git am --empty-commit=skip cover-letter.patch >err &&
     ++	git am --empty=drop cover-letter.patch >err &&
      +	test_path_is_missing .git/rebase-apply &&
      +	test_cmp_rev empty-commit^ HEAD
      +'
      +
      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
     -+	git am --empty-commit=asis empty-commit.patch &&
     ++	git am --empty=keep empty-commit.patch &&
      +	test_path_is_missing .git/rebase-apply &&
      +	{
      +		git show empty-commit --format="%B" &&
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +	git show HEAD --format="%B" >actual &&
      +	test_cmp actual expected &&
      +
     -+	git am --empty-commit=asis cover-letter.patch &&
     ++	git am --empty=keep cover-letter.patch &&
      +	test_path_is_missing .git/rebase-apply &&
      +	{
      +		echo "*** SUBJECT HERE ***" &&

-- 
gitgitgadget

[PATCH v5 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-17 09:34:02

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v5 2/2] am: support --empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-17 09:34:06

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..665bc89ca9f 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty-commit=(die|drop|keep)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'drop', skip such an e-mail message without outputting error.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..1a3ed87b445 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip without outputting errors */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty_commit(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (unset || !strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			if (state->empty_type == DROP_EMPTY_COMMIT)
+				goto next;
+			else if (state->empty_type == KEEP_EMPTY_COMMIT) {
+				if (run_applypatch_msg_hook(state))
+					exit(1);
+				else
+					goto commit;
+			} else if (state->empty_type == DIE_EMPTY_COMMIT) {
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		{ OPTION_CALLBACK, 0, "empty", &state.empty_type,
+		  "(die|drop|keep)",
+		  N_("specify how to handle empty patches"),
+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..3119556884d 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,13 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'still output error with --empty when meeting empty files' '
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty=die cover-letter.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
+	git am --empty=drop empty-commit.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+
+	git am --empty=drop cover-letter.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	test_cmp_rev empty-commit^ HEAD
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected &&
+
+	git am --empty=keep cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		echo "*** SUBJECT HERE ***" &&
+		echo &&
+		echo "*** BLURB HERE ***" &&
+		echo &&
+		echo "A U Thor (1):" &&
+		printf "  " &&
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v6 1/3] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-18 10:51:05

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-18 10:51:10

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Aleen (3):
  doc: git-format-patch: describe the option --always
  am: support --empty option to handle empty patches
  am: throw an error when passing --empty option without value

 Documentation/git-am.txt           |  9 ++++
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 49 ++++++++++++++++--
 t/t4150-am.sh                      | 79 ++++++++++++++++++++++++++++++
 4 files changed, 137 insertions(+), 6 deletions(-)


base-commit: b550198c73edd4cc058832dcf74b41aeec2adba2
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v5:

 1:  9f1b3dd6d0b = 1:  9f1b3dd6d0b doc: git-format-patch: describe the option --always
 2:  96d8573dc80 = 2:  96d8573dc80 am: support --empty option to handle empty patches
 -:  ----------- > 3:  e907a2b2faa am: throw an error when passing --empty option without value

-- 
gitgitgadget

[PATCH v6 2/3] am: support --empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-18 10:51:12

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..665bc89ca9f 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty-commit=(die|drop|keep)::
+	The command usually errors out when seeing an input e-mail
+	message that lacks a patch. When this option is set to
+	'drop', skip such an e-mail message without outputting error.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+	'die' is specified by default.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..1a3ed87b445 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip without outputting errors */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,23 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty_commit(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	if (unset || !strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1272,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1792,6 +1811,20 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			if (state->empty_type == DROP_EMPTY_COMMIT)
+				goto next;
+			else if (state->empty_type == KEEP_EMPTY_COMMIT) {
+				if (run_applypatch_msg_hook(state))
+					exit(1);
+				else
+					goto commit;
+			} else if (state->empty_type == DIE_EMPTY_COMMIT) {
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
 
@@ -1827,6 +1860,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2391,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		{ OPTION_CALLBACK, 0, "empty", &state.empty_type,
+		  "(die|drop|keep)",
+		  N_("specify how to handle empty patches"),
+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..3119556884d 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,13 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	git format-patch --stdout empty-commit^ >empty.patch &&
+	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1159,70 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'still output error with --empty when meeting empty files' '
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply &&
+
+	test_must_fail git am --empty=die cover-letter.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	test_i18ngrep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
+	git am --empty=drop empty-commit.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+
+	git am --empty=drop cover-letter.patch >err &&
+	test_path_is_missing .git/rebase-apply &&
+	test_cmp_rev empty-commit^ HEAD
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected &&
+
+	git am --empty=keep cover-letter.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	{
+		echo "*** SUBJECT HERE ***" &&
+		echo &&
+		echo "*** BLURB HERE ***" &&
+		echo &&
+		echo "A U Thor (1):" &&
+		printf "  " &&
+		git show empty-commit --format="%B" &&
+		echo "--" &&
+		git version | sed -e "s/^git version //" &&
+		echo
+	} >expected &&
+	git show HEAD --format="%B" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v6 3/3] am: throw an error when passing --empty option without value

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-18 10:51:13

From: Aleen <redacted>

Signed-off-by: Aleen <redacted>
---
 builtin/am.c  | 13 +++++++------
 t/t4150-am.sh |  8 +++++++-
 2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/builtin/am.c b/builtin/am.c
index 1a3ed87b445..5d487b5256b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -185,12 +185,14 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
-static int am_option_parse_empty_commit(const struct option *opt,
+static int am_option_parse_empty(const struct option *opt,
 				     const char *arg, int unset)
 {
 	int *opt_value = opt->value;
 
-	if (unset || !strcmp(arg, "die"))
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
 		*opt_value = DIE_EMPTY_COMMIT;
 	else if (!strcmp(arg, "drop"))
 		*opt_value = DROP_EMPTY_COMMIT;
@@ -2391,10 +2393,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
-		{ OPTION_CALLBACK, 0, "empty", &state.empty_type,
-		  "(die|drop|keep)",
-		  N_("specify how to handle empty patches"),
-		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 3119556884d..c32d21e80da 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1165,8 +1165,14 @@ test_expect_success 'still output error with --empty when meeting empty files' '
 	test_cmp expected actual
 '
 
-test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
+test_expect_success 'invalid when passing no value for the --empty option' '
 	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
 	test_must_fail git am empty-commit.patch >err &&
 	test_path_is_dir .git/rebase-apply &&
 	test_i18ngrep "Patch is empty." err &&
-- 
gitgitgadget

[PATCH v7 0/2] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-19 05:05:08

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

cc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:
Aleen 徐沛文 pwxu@coremail.cn

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches

 Documentation/git-am.txt           |  8 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 55 +++++++++++++++++++++++++++---
 po/bg.po                           |  2 +-
 po/ca.po                           |  2 +-
 po/de.po                           |  2 +-
 po/el.po                           |  2 +-
 po/es.po                           |  2 +-
 po/fr.po                           |  2 +-
 po/git.pot                         |  2 +-
 po/id.po                           |  2 +-
 po/it.po                           |  2 +-
 po/ko.po                           |  2 +-
 po/pl.po                           |  2 +-
 po/pt_PT.po                        |  8 ++---
 po/ru.po                           |  4 +--
 po/sv.po                           |  2 +-
 po/tr.po                           |  2 +-
 po/vi.po                           |  2 +-
 po/zh_CN.po                        |  9 +++--
 po/zh_TW.po                        |  7 +++-
 t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++
 22 files changed, 145 insertions(+), 29 deletions(-)


base-commit: ca35af825273b98fc8dc11527488952f5db8eb80
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v6:

 1:  9f1b3dd6d0b ! 1:  d612bc49d57 doc: git-format-patch: describe the option --always
     @@ Metadata
       ## Commit message ##
          doc: git-format-patch: describe the option --always
      
     +    This commit has described how to use '--always' option in the command
     +    'git-format-patch' to include patches for commits that emit no changes.
     +
          Signed-off-by: Aleen [off-list ref]
      
       ## Documentation/git-format-patch.txt ##
 2:  96d8573dc80 ! 2:  9e60e77c041 am: support --empty option to handle empty patches
     @@ Metadata
      Author: Aleen [off-list ref]
      
       ## Commit message ##
     -    am: support --empty option to handle empty patches
     +    am: support --empty=<option> to handle empty patches
     +
     +    Since that the command 'git-format-patch' can include patches of
     +    commits that emit no changes, the 'git-am' command should also
     +    support an option, named as '--empty', to specify how to handle
     +    those empty patches. In this commit, we have implemented three
     +    valid options ('die', 'drop' and 'keep').
      
          Signed-off-by: Aleen [off-list ref]
      
     @@ Documentation/git-am.txt: OPTIONS
       	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
       
      +--empty-commit=(die|drop|keep)::
     -+	The command usually errors out when seeing an input e-mail
     -+	message that lacks a patch. When this option is set to
     -+	'drop', skip such an e-mail message without outputting error.
     ++	By default, or when the option is set to 'die', the command
     ++	errors out on an input e-mail message that lacks a patch. When
     ++	this option is set to 'drop', skip such an e-mail message instead.
      +	When this option is set to 'keep', create an empty commit,
      +	recording the contents of the e-mail message as its log.
     -+	'die' is specified by default.
      +
       -m::
       --message-id::
     @@ builtin/am.c: enum show_patch_type {
       
      +enum empty_action {
      +	DIE_EMPTY_COMMIT = 0,  /* output errors */
     -+	DROP_EMPTY_COMMIT,     /* skip without outputting errors */
     ++	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
      +	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
      +};
      +
     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,
       	return 0;
       }
       
     -+static int am_option_parse_empty_commit(const struct option *opt,
     ++static int am_option_parse_empty(const struct option *opt,
      +				     const char *arg, int unset)
      +{
      +	int *opt_value = opt->value;
      +
     -+	if (unset || !strcmp(arg, "die"))
     ++	BUG_ON_OPT_NEG(unset);
     ++
     ++	if (!strcmp(arg, "die"))
      +		*opt_value = DIE_EMPTY_COMMIT;
      +	else if (!strcmp(arg, "drop"))
      +		*opt_value = DROP_EMPTY_COMMIT;
     @@ builtin/am.c: static int parse_mail(struct am_state *state, const char *mail)
       	strbuf_addstr(&msg, "\n\n");
       	strbuf_addbuf(&msg, &mi.log_message);
       	strbuf_stripspace(&msg, 0);
     +@@ builtin/am.c: static void am_run(struct am_state *state, int resume)
     + 	while (state->cur <= state->last) {
     + 		const char *mail = am_path(state, msgnum(state));
     + 		int apply_status;
     ++		int to_keep;
     + 
     + 		reset_ident_date();
     + 
      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
       		if (state->interactive && do_interactive(state))
       			goto next;
       
     ++		to_keep = 0;
      +		if (is_empty_or_missing_file(am_path(state, "patch"))) {
     -+			if (state->empty_type == DROP_EMPTY_COMMIT)
     ++			switch (state->empty_type) {
     ++			case DROP_EMPTY_COMMIT:
     ++				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
      +				goto next;
     -+			else if (state->empty_type == KEEP_EMPTY_COMMIT) {
     -+				if (run_applypatch_msg_hook(state))
     -+					exit(1);
     -+				else
     -+					goto commit;
     -+			} else if (state->empty_type == DIE_EMPTY_COMMIT) {
     ++				break;
     ++			case KEEP_EMPTY_COMMIT:
     ++				to_keep = 1;
     ++				break;
     ++			case DIE_EMPTY_COMMIT:
      +				printf_ln(_("Patch is empty."));
      +				die_user_resolve(state);
     ++				break;
      +			}
      +		}
      +
       		if (run_applypatch_msg_hook(state))
       			exit(1);
     ++		if (to_keep)
     ++			goto commit;
     + 
     + 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
       
      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
       			die_user_resolve(state);
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
       		  N_("GPG-sign commits"),
       		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
     -+		{ OPTION_CALLBACK, 0, "empty", &state.empty_type,
     -+		  "(die|drop|keep)",
     -+		  N_("specify how to handle empty patches"),
     -+		  PARSE_OPT_OPTARG, am_option_parse_empty_commit },
     ++		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
     ++		  N_("how to handle empty patches"),
     ++		  PARSE_OPT_NONEG, am_option_parse_empty),
       		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
       			N_("(internal use for git-rebase)")),
       		OPT_END()
      
     + ## po/bg.po ##
     +@@ po/bg.po: msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr ""
     + "Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Прилагане: %.*s"
     +
     + ## po/ca.po ##
     +@@ po/ca.po: msgstr "no s'ha pogut escriure el fitxer d'índex"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Índex brut: no es poden aplicar pedaços (bruts: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "S'està aplicant: %.*s"
     +
     + ## po/de.po ##
     +@@ po/de.po: msgstr "Konnte Index-Datei nicht schreiben."
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Geänderter Index: kann Patches nicht anwenden (geändert: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Wende an: %.*s"
     +
     + ## po/el.po ##
     +@@ po/el.po: msgstr "Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr ""
     + 
     +-#: builtin/am.c:1749 builtin/am.c:1817
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr ""
     +
     + ## po/es.po ##
     +@@ po/es.po: msgstr "no es posible escribir el archivo índice"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Índice sucio: no se puede aplicar parches (sucio: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Aplicando: %.*s"
     +
     + ## po/fr.po ##
     +@@ po/fr.po: msgstr "impossible d'écrire le fichier d'index"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Index sale : impossible d'appliquer des patchs (sales : %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Application de  %.*s"
     +
     + ## po/git.pot ##
     +@@ po/git.pot: msgstr ""
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr ""
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr ""
     +
     + ## po/id.po ##
     +@@ po/id.po: msgstr "tidak dapat menulis berkas indeks"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Menerapkan: %.*s"
     +
     + ## po/it.po ##
     +@@ po/it.po: msgstr "impossibile scrivere il file indice"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Indice sporco: impossibile applicare le patch (elemento sporco: %s)"
     + 
     +-#: builtin/am.c:1761 builtin/am.c:1829
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Applicazione in corso: %.*s"
     +
     + ## po/ko.po ##
     +@@ po/ko.po: msgstr "적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)"
     + 
     +-#: builtin/am.c:1808 builtin/am.c:1879
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "적용하는 중: %.*s"
     +
     + ## po/pl.po ##
     +@@ po/pl.po: msgstr "nie można zapisać pliku indeksu"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Brudny indeks: nie można zastosować łatek (brudny: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Stosowanie: %.*s"
     +
     + ## po/pt_PT.po ##
     +@@
     + #   bisect                           |  bisetar
     + #   blame                            |  blame
     + #   blob object                      |  objeto-blob
     +-#   branch                           |  ramo 
     ++#   branch                           |  ramo
     + #   bug                              |  bug
     + #   bundle                           |  conjunto
     + #   bypass                           |  desviar
     +@@
     + #   loose refs                       |  refs soltas
     + #   mark                             |  marca
     + #   master                           |  master
     +-#   merge                            |  junção 
     ++#   merge                            |  junção
     + #   mergetag                         |  etiqueta-junção
     + #   object                           |  objeto
     + #   object database                  |  base dados de objeto
     +@@
     + #   token                            |  token
     + #   unset                            |  desdefinir
     + #   untrack                          |  desmonitorizar
     +-#   
     ++#
     + #
     + msgid ""
     + msgstr ""
     +@@ po/pt_PT.po: msgstr "incapaz escrever ficheiro de index"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Index sujo: incapaz aplicar patches (sujo: %s)"
     + 
     +-#: builtin/am.c:1797 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "A aplicar: %.*s"
     +
     + ## po/ru.po ##
     +@@
     + # SOME DESCRIPTIVE TITLE.
     + # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
     + # This file is distributed under the same license as the PACKAGE package.
     +-# 
     ++#
     + # Translators:
     + # Alexander Golubev [off-list ref], 2020
     + # Dimitriy Ryazantcev [off-list ref], 2014-2021
     +@@ po/ru.po: msgstr "не удалось записать индекс"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Индекс изменён: нельзя применять патчи (изменено: %s)"
     + 
     +-#: builtin/am.c:1748 builtin/am.c:1816
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Применение: %.*s"
     +
     + ## po/sv.po ##
     +@@ po/sv.po: msgstr "kan inte skriva indexfil"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Tillämpar: %.*s"
     +
     + ## po/tr.po ##
     +@@ po/tr.po: msgstr "indeks dosyası yazılamıyor"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Kirli indeks: Yamalar uygulanamıyor (kirli: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Uygulanıyor: %.*s"
     +
     + ## po/vi.po ##
     +@@ po/vi.po: msgstr "không thể ghi tập tin lưu mục lục"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "Áp dụng: %.*s"
     +
     + ## po/zh_CN.po ##
     +@@ po/zh_CN.po: msgstr "坏的索引文件 sha1 签名"
     + msgid "index uses %.4s extension, which we do not understand"
     + msgstr "索引使用不被支持的 %.4s 扩展"
     + 
     +-# 	
     ++#
     + #: read-cache.c:1834
     + #, c-format
     + msgid "ignoring %.4s extension"
     +@@ po/zh_CN.po: msgstr "无法写入索引文件"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "脏索引:不能应用补丁(脏文件:%s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "应用:%.*s"
     + 
     ++#: builtin/am.c:1818
     ++#, c-format
     ++msgid "Skipping: %.*s"
     ++msgstr "跳过:%.*s"
     ++
     + #: builtin/am.c:1815
     + msgid "No changes -- Patch already applied."
     + msgstr "没有变更 —— 补丁已经应用过。"
     +
     + ## po/zh_TW.po ##
     +@@ po/zh_TW.po: msgstr "無法寫入索引檔案"
     + msgid "Dirty index: cannot apply patches (dirty: %s)"
     + msgstr "髒索引:不能套用修補檔(髒檔案:%s)"
     + 
     +-#: builtin/am.c:1798 builtin/am.c:1865
     ++#: builtin/am.c:1834 builtin/am.c:1902
     + #, c-format
     + msgid "Applying: %.*s"
     + msgstr "套用:%.*s"
     + 
     ++#: builtin/am.c:1818
     ++#, c-format
     ++msgid "Skipping: %.*s"
     ++msgstr "忽略:%.*s"
     ++
     + #: builtin/am.c:1815
     + msgid "No changes -- Patch already applied."
     + msgstr "沒有變更——修補檔已經套用過。"
     +
       ## t/t4150-am.sh ##
      @@ t/t4150-am.sh: test_expect_success setup '
       
     @@ t/t4150-am.sh: test_expect_success setup '
      +	git checkout -b empty-commit &&
      +	git commit -m "empty commit" --allow-empty &&
      +
     -+	git format-patch --stdout empty-commit^ >empty.patch &&
     -+	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&
     ++	: >empty.patch &&
      +	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
      +
       	# reset time
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
       	git -C client am ../patch
       '
       
     -+test_expect_success 'still output error with --empty when meeting empty files' '
     ++test_expect_success 'An empty input file is error regardless of --empty option' '
      +	test_must_fail git am --empty=drop empty.patch 2>actual &&
      +	echo Patch format detection failed. >expected &&
      +	test_cmp expected actual
      +'
      +
     -+test_expect_success 'error when meeting e-mail message that lacks a patch by default' '
     ++test_expect_success 'invalid when passing the --empty option alone' '
      +	git checkout empty-commit^ &&
     ++	test_must_fail git am --empty empty-commit.patch 2>err &&
     ++	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
     ++	test_cmp expected err
     ++'
     ++
     ++test_expect_success 'a message without a patch is an error (default)' '
     ++	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am empty-commit.patch >err &&
     -+	test_path_is_dir .git/rebase-apply &&
     -+	test_i18ngrep "Patch is empty." err &&
     -+	rm -fr .git/rebase-apply &&
     ++	grep "Patch is empty" err &&
     ++	rm -fr .git/rebase-apply
     ++'
      +
     ++test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
     ++	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am --empty=die empty-commit.patch >err &&
     -+	test_path_is_dir .git/rebase-apply &&
     -+	test_i18ngrep "Patch is empty." err &&
     -+	rm -fr .git/rebase-apply &&
     -+
     -+	test_must_fail git am --empty=die cover-letter.patch >err &&
     -+	test_path_is_dir .git/rebase-apply &&
     -+	test_i18ngrep "Patch is empty." err &&
     ++	grep "Patch is empty." err &&
      +	rm -fr .git/rebase-apply
      +'
      +
     -+test_expect_success 'skip without error when meeting e-mail message that lacks a patch' '
     -+	git am --empty=drop empty-commit.patch >err &&
     -+	test_path_is_missing .git/rebase-apply &&
     ++test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
     ++	git am --empty=drop empty-commit.patch >output &&
      +	git rev-parse empty-commit^ >expected &&
      +	git rev-parse HEAD >actual &&
      +	test_cmp expected actual &&
     -+
     -+	git am --empty=drop cover-letter.patch >err &&
     -+	test_path_is_missing .git/rebase-apply &&
     -+	test_cmp_rev empty-commit^ HEAD
     ++	grep "Skipping: empty commit" output
      +'
      +
      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
      +	git am --empty=keep empty-commit.patch &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	{
     -+		git show empty-commit --format="%B" &&
     -+		echo "--" &&
     -+		git version | sed -e "s/^git version //" &&
     -+		echo
     -+	} >expected &&
     -+	git show HEAD --format="%B" >actual &&
     -+	test_cmp actual expected &&
     -+
     -+	git am --empty=keep cover-letter.patch &&
     -+	test_path_is_missing .git/rebase-apply &&
     -+	{
     -+		echo "*** SUBJECT HERE ***" &&
     -+		echo &&
     -+		echo "*** BLURB HERE ***" &&
     -+		echo &&
     -+		echo "A U Thor (1):" &&
     -+		printf "  " &&
     -+		git show empty-commit --format="%B" &&
     -+		echo "--" &&
     -+		git version | sed -e "s/^git version //" &&
     -+		echo
     -+	} >expected &&
     -+	git show HEAD --format="%B" >actual &&
     ++	git show empty-commit --format="%s" >expected &&
     ++	git show HEAD --format="%s" >actual &&
      +	test_cmp actual expected
      +'
      +
 3:  e907a2b2faa < -:  ----------- am: throw an error when passing --empty option without value

-- 
gitgitgadget

[PATCH v7 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-19 05:05:11

From: Aleen <redacted>

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: Aleen <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v7 2/2] am: support --empty=<option> to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-19 05:05:11

From: Aleen <redacted>

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: Aleen <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 po/bg.po                 |  2 +-
 po/ca.po                 |  2 +-
 po/de.po                 |  2 +-
 po/el.po                 |  2 +-
 po/es.po                 |  2 +-
 po/fr.po                 |  2 +-
 po/git.pot               |  2 +-
 po/id.po                 |  2 +-
 po/it.po                 |  2 +-
 po/ko.po                 |  2 +-
 po/pl.po                 |  2 +-
 po/pt_PT.po              |  8 +++---
 po/ru.po                 |  4 +--
 po/sv.po                 |  2 +-
 po/tr.po                 |  2 +-
 po/vi.po                 |  2 +-
 po/zh_CN.po              |  9 +++++--
 po/zh_TW.po              |  7 ++++-
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 21 files changed, 140 insertions(+), 28 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..1e6a64bf2ed 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty-commit=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/po/bg.po b/po/bg.po
index 51d7c7242f2..17d05bc9d4e 100644
--- a/po/bg.po
+++ b/po/bg.po
@@ -11194,7 +11194,7 @@ msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr ""
 "Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Прилагане: %.*s"
diff --git a/po/ca.po b/po/ca.po
index 556b028cd8b..4035055c28e 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -10931,7 +10931,7 @@ msgstr "no s'ha pogut escriure el fitxer d'índex"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Índex brut: no es poden aplicar pedaços (bruts: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "S'està aplicant: %.*s"
diff --git a/po/de.po b/po/de.po
index f00c21d896f..fbae81fc59f 100644
--- a/po/de.po
+++ b/po/de.po
@@ -10999,7 +10999,7 @@ msgstr "Konnte Index-Datei nicht schreiben."
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Geänderter Index: kann Patches nicht anwenden (geändert: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Wende an: %.*s"
diff --git a/po/el.po b/po/el.po
index 703f46d0c7c..5a3d4d8a61e 100644
--- a/po/el.po
+++ b/po/el.po
@@ -7801,7 +7801,7 @@ msgstr "Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr ""
 
-#: builtin/am.c:1749 builtin/am.c:1817
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr ""
diff --git a/po/es.po b/po/es.po
index ced2eb6d3d5..774a333cc64 100644
--- a/po/es.po
+++ b/po/es.po
@@ -10840,7 +10840,7 @@ msgstr "no es posible escribir el archivo índice"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Índice sucio: no se puede aplicar parches (sucio: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Aplicando: %.*s"
diff --git a/po/fr.po b/po/fr.po
index e950f87add0..30a8a1b3fd1 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -11013,7 +11013,7 @@ msgstr "impossible d'écrire le fichier d'index"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Index sale : impossible d'appliquer des patchs (sales : %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Application de  %.*s"
diff --git a/po/git.pot b/po/git.pot
index 78fea37065b..1367e12f3d0 100644
--- a/po/git.pot
+++ b/po/git.pot
@@ -9985,7 +9985,7 @@ msgstr ""
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr ""
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr ""
diff --git a/po/id.po b/po/id.po
index c1afc2a32f3..0821da2efee 100644
--- a/po/id.po
+++ b/po/id.po
@@ -10306,7 +10306,7 @@ msgstr "tidak dapat menulis berkas indeks"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Menerapkan: %.*s"
diff --git a/po/it.po b/po/it.po
index c31560834d8..475a60dc1d1 100644
--- a/po/it.po
+++ b/po/it.po
@@ -10229,7 +10229,7 @@ msgstr "impossibile scrivere il file indice"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Indice sporco: impossibile applicare le patch (elemento sporco: %s)"
 
-#: builtin/am.c:1761 builtin/am.c:1829
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Applicazione in corso: %.*s"
diff --git a/po/ko.po b/po/ko.po
index 5d190e52380..5a9d7eb0305 100644
--- a/po/ko.po
+++ b/po/ko.po
@@ -5681,7 +5681,7 @@ msgstr "적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)"
 
-#: builtin/am.c:1808 builtin/am.c:1879
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "적용하는 중: %.*s"
diff --git a/po/pl.po b/po/pl.po
index 0ec127e14cc..f9768843fb0 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -10811,7 +10811,7 @@ msgstr "nie można zapisać pliku indeksu"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Brudny indeks: nie można zastosować łatek (brudny: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Stosowanie: %.*s"
diff --git a/po/pt_PT.po b/po/pt_PT.po
index 70e5c6d9c16..e89894ca5dc 100644
--- a/po/pt_PT.po
+++ b/po/pt_PT.po
@@ -24,7 +24,7 @@
 #   bisect                           |  bisetar
 #   blame                            |  blame
 #   blob object                      |  objeto-blob
-#   branch                           |  ramo 
+#   branch                           |  ramo
 #   bug                              |  bug
 #   bundle                           |  conjunto
 #   bypass                           |  desviar
@@ -74,7 +74,7 @@
 #   loose refs                       |  refs soltas
 #   mark                             |  marca
 #   master                           |  master
-#   merge                            |  junção 
+#   merge                            |  junção
 #   mergetag                         |  etiqueta-junção
 #   object                           |  objeto
 #   object database                  |  base dados de objeto
@@ -211,7 +211,7 @@
 #   token                            |  token
 #   unset                            |  desdefinir
 #   untrack                          |  desmonitorizar
-#   
+#
 #
 msgid ""
 msgstr ""
@@ -10485,7 +10485,7 @@ msgstr "incapaz escrever ficheiro de index"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Index sujo: incapaz aplicar patches (sujo: %s)"
 
-#: builtin/am.c:1797 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "A aplicar: %.*s"
diff --git a/po/ru.po b/po/ru.po
index 993d106f1f9..69acd080d12 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -1,7 +1,7 @@
 # SOME DESCRIPTIVE TITLE.
 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 # This file is distributed under the same license as the PACKAGE package.
-# 
+#
 # Translators:
 # Alexander Golubev <fatzer2@gmail.com>, 2020
 # Dimitriy Ryazantcev <DJm00n@mail.ru>, 2014-2021
@@ -9398,7 +9398,7 @@ msgstr "не удалось записать индекс"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Индекс изменён: нельзя применять патчи (изменено: %s)"
 
-#: builtin/am.c:1748 builtin/am.c:1816
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Применение: %.*s"
diff --git a/po/sv.po b/po/sv.po
index e3b17b6a0ef..41f6be1c540 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -10746,7 +10746,7 @@ msgstr "kan inte skriva indexfil"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Tillämpar: %.*s"
diff --git a/po/tr.po b/po/tr.po
index 06870900c4e..6c4d7ee8337 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -10810,7 +10810,7 @@ msgstr "indeks dosyası yazılamıyor"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Kirli indeks: Yamalar uygulanamıyor (kirli: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Uygulanıyor: %.*s"
diff --git a/po/vi.po b/po/vi.po
index be197ab594f..5ed8c0239fe 100644
--- a/po/vi.po
+++ b/po/vi.po
@@ -10807,7 +10807,7 @@ msgstr "không thể ghi tập tin lưu mục lục"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "Áp dụng: %.*s"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 60ffa45f7a0..13c4ae14a29 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -6463,7 +6463,7 @@ msgstr "坏的索引文件 sha1 签名"
 msgid "index uses %.4s extension, which we do not understand"
 msgstr "索引使用不被支持的 %.4s 扩展"
 
-# 	
+#
 #: read-cache.c:1834
 #, c-format
 msgid "ignoring %.4s extension"
@@ -10673,11 +10673,16 @@ msgstr "无法写入索引文件"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "脏索引:不能应用补丁(脏文件:%s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "应用:%.*s"
 
+#: builtin/am.c:1818
+#, c-format
+msgid "Skipping: %.*s"
+msgstr "跳过:%.*s"
+
 #: builtin/am.c:1815
 msgid "No changes -- Patch already applied."
 msgstr "没有变更 —— 补丁已经应用过。"
diff --git a/po/zh_TW.po b/po/zh_TW.po
index f1df8b60a91..d6259cc1cb6 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -10538,11 +10538,16 @@ msgstr "無法寫入索引檔案"
 msgid "Dirty index: cannot apply patches (dirty: %s)"
 msgstr "髒索引:不能套用修補檔(髒檔案:%s)"
 
-#: builtin/am.c:1798 builtin/am.c:1865
+#: builtin/am.c:1834 builtin/am.c:1902
 #, c-format
 msgid "Applying: %.*s"
 msgstr "套用:%.*s"
 
+#: builtin/am.c:1818
+#, c-format
+msgid "Skipping: %.*s"
+msgstr "忽略:%.*s"
+
 #: builtin/am.c:1815
 msgid "No changes -- Patch already applied."
 msgstr "沒有變更——修補檔已經套用過。"
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..16e966b2b73 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'An empty input file is error regardless of --empty option' '
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	rm -fr .git/rebase-apply
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v6 2/3] am: support --empty option to handle empty patches

From: Bagas Sanjaya <hidden>
Date: 2021-11-19 10:33:47

On 18/11/21 17.50, Aleen via GitGitGadget wrote:
+test_expect_success 'still output error with --empty when meeting empty files' '
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
Why isn't the echo string quoted?

-- 
An old man doll... just what I always wanted! - Clara

Re: [PATCH v6 2/3] am: support --empty option to handle empty patches

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2021-11-19 12:12:52

On Fri, Nov 19 2021, Bagas Sanjaya wrote:
On 18/11/21 17.50, Aleen via GitGitGadget wrote:
quoted
+test_expect_success 'still output error with --empty when meeting empty files' '
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo Patch format detection failed. >expected &&
+	test_cmp expected actual
+'
Why isn't the echo string quoted?
There's no need to quote the arguments given to echo in cases like
these.

It's not the same, i.e. it'll get N arguments on argv and not on, but it
makes it easier to spot things that do need quoting, rather than
over-quoting everything.

Re: [PATCH v6 2/3] am: support --empty option to handle empty patches

From: Eric Sunshine <hidden>
Date: 2021-11-19 12:20:37

On Fri, Nov 19, 2021 at 7:12 AM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
On Fri, Nov 19 2021, Bagas Sanjaya wrote:
quoted
On 18/11/21 17.50, Aleen via GitGitGadget wrote:
quoted
+test_expect_success 'still output error with --empty when meeting empty files' '
+    test_must_fail git am --empty=drop empty.patch 2>actual &&
+    echo Patch format detection failed. >expected &&
+    test_cmp expected actual
+'
Why isn't the echo string quoted?
There's no need to quote the arguments given to echo in cases like
these.

It's not the same, i.e. it'll get N arguments on argv and not on, but it
makes it easier to spot things that do need quoting, rather than
over-quoting everything.
I recently expressed an opposing opinion in [1], stating effectively
that omitting the quotes like this is "an accident waiting to happen":

    ... the lack of quotes ... in the `echo ... >expect` statement
    gives me a moment's pause since it relies upon the fact that
    `echo` will insert exactly one space between the ... arguments
    (which happens to match the single space in the [command's output]
    ). For clarity and that extra bit of robustness, I'd probably have
    used a single double-quoted string argument with `echo`.

But, it's a fairly minor objection.

[1]: https://lore.kernel.org/git/CAPig+cQVSUg1aqry_hMydJ=Uo=-VhOog6TUTpG=0on0LUcw8Dg@mail.gmail.com/

[PATCH v8 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 06:46:57

From: Aleen <redacted>

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: Aleen 徐沛文 <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v8 0/2] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 06:46:59

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. update code according to the seen branch
 2. fix wrong document of git-am

----------------------------------------------------------------------------

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches

 Documentation/git-am.txt           |  8 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 55 +++++++++++++++++++++++++++---
 t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 6 deletions(-)


base-commit: ca35af825273b98fc8dc11527488952f5db8eb80
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v8
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v7:

 1:  d612bc49d57 ! 1:  5d98a088e14 doc: git-format-patch: describe the option --always
     @@ Commit message
          This commit has described how to use '--always' option in the command
          'git-format-patch' to include patches for commits that emit no changes.
      
     -    Signed-off-by: Aleen [off-list ref]
     +    Signed-off-by: Aleen 徐沛文 [off-list ref]
      
       ## Documentation/git-format-patch.txt ##
      @@ Documentation/git-format-patch.txt: SYNOPSIS
 2:  9e60e77c041 ! 2:  3ff18e16a7a am: support --empty=<option> to handle empty patches
     @@ Commit message
          those empty patches. In this commit, we have implemented three
          valid options ('die', 'drop' and 'keep').
      
     -    Signed-off-by: Aleen [off-list ref]
     +    Signed-off-by: Aleen 徐沛文 [off-list ref]
      
       ## Documentation/git-am.txt ##
      @@ Documentation/git-am.txt: SYNOPSIS
     @@ Documentation/git-am.txt: OPTIONS
       --quoted-cr=<action>::
       	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
       
     -+--empty-commit=(die|drop|keep)::
     ++--empty=(die|drop|keep)::
      +	By default, or when the option is set to 'die', the command
      +	errors out on an input e-mail message that lacks a patch. When
      +	this option is set to 'drop', skip such an e-mail message instead.
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       			N_("(internal use for git-rebase)")),
       		OPT_END()
      
     - ## po/bg.po ##
     -@@ po/bg.po: msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr ""
     - "Индексът не е чист: кръпките не може да бъдат приложени (замърсени са: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Прилагане: %.*s"
     -
     - ## po/ca.po ##
     -@@ po/ca.po: msgstr "no s'ha pogut escriure el fitxer d'índex"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Índex brut: no es poden aplicar pedaços (bruts: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "S'està aplicant: %.*s"
     -
     - ## po/de.po ##
     -@@ po/de.po: msgstr "Konnte Index-Datei nicht schreiben."
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Geänderter Index: kann Patches nicht anwenden (geändert: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Wende an: %.*s"
     -
     - ## po/el.po ##
     -@@ po/el.po: msgstr "Να γίνει εφαρμογή; [y]es/[n]o/[e]dit/[v]iew patch/[a]ccep
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr ""
     - 
     --#: builtin/am.c:1749 builtin/am.c:1817
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr ""
     -
     - ## po/es.po ##
     -@@ po/es.po: msgstr "no es posible escribir el archivo índice"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Índice sucio: no se puede aplicar parches (sucio: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Aplicando: %.*s"
     -
     - ## po/fr.po ##
     -@@ po/fr.po: msgstr "impossible d'écrire le fichier d'index"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Index sale : impossible d'appliquer des patchs (sales : %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Application de  %.*s"
     -
     - ## po/git.pot ##
     -@@ po/git.pot: msgstr ""
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr ""
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr ""
     -
     - ## po/id.po ##
     -@@ po/id.po: msgstr "tidak dapat menulis berkas indeks"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Indeks kotor: tidak dapat menerapkan tambalan (kotor: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Menerapkan: %.*s"
     -
     - ## po/it.po ##
     -@@ po/it.po: msgstr "impossibile scrivere il file indice"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Indice sporco: impossibile applicare le patch (elemento sporco: %s)"
     - 
     --#: builtin/am.c:1761 builtin/am.c:1829
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Applicazione in corso: %.*s"
     -
     - ## po/ko.po ##
     -@@ po/ko.po: msgstr "적용? 예[y]/아니오[n]/편집[e]/패치 보기[v]/모두 적용[a]:
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "변경된 인덱스: 패치를 적용할 수 없습니다 (dirty: %s)"
     - 
     --#: builtin/am.c:1808 builtin/am.c:1879
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "적용하는 중: %.*s"
     -
     - ## po/pl.po ##
     -@@ po/pl.po: msgstr "nie można zapisać pliku indeksu"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Brudny indeks: nie można zastosować łatek (brudny: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Stosowanie: %.*s"
     -
     - ## po/pt_PT.po ##
     -@@
     - #   bisect                           |  bisetar
     - #   blame                            |  blame
     - #   blob object                      |  objeto-blob
     --#   branch                           |  ramo 
     -+#   branch                           |  ramo
     - #   bug                              |  bug
     - #   bundle                           |  conjunto
     - #   bypass                           |  desviar
     -@@
     - #   loose refs                       |  refs soltas
     - #   mark                             |  marca
     - #   master                           |  master
     --#   merge                            |  junção 
     -+#   merge                            |  junção
     - #   mergetag                         |  etiqueta-junção
     - #   object                           |  objeto
     - #   object database                  |  base dados de objeto
     -@@
     - #   token                            |  token
     - #   unset                            |  desdefinir
     - #   untrack                          |  desmonitorizar
     --#   
     -+#
     - #
     - msgid ""
     - msgstr ""
     -@@ po/pt_PT.po: msgstr "incapaz escrever ficheiro de index"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Index sujo: incapaz aplicar patches (sujo: %s)"
     - 
     --#: builtin/am.c:1797 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "A aplicar: %.*s"
     -
     - ## po/ru.po ##
     -@@
     - # SOME DESCRIPTIVE TITLE.
     - # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
     - # This file is distributed under the same license as the PACKAGE package.
     --# 
     -+#
     - # Translators:
     - # Alexander Golubev [off-list ref], 2020
     - # Dimitriy Ryazantcev [off-list ref], 2014-2021
     -@@ po/ru.po: msgstr "не удалось записать индекс"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Индекс изменён: нельзя применять патчи (изменено: %s)"
     - 
     --#: builtin/am.c:1748 builtin/am.c:1816
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Применение: %.*s"
     -
     - ## po/sv.po ##
     -@@ po/sv.po: msgstr "kan inte skriva indexfil"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Smutsigt index: kan inte tillämpa patchar (smutsiga: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Tillämpar: %.*s"
     -
     - ## po/tr.po ##
     -@@ po/tr.po: msgstr "indeks dosyası yazılamıyor"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Kirli indeks: Yamalar uygulanamıyor (kirli: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Uygulanıyor: %.*s"
     -
     - ## po/vi.po ##
     -@@ po/vi.po: msgstr "không thể ghi tập tin lưu mục lục"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "Bảng mục lục bẩn: không thể áp dụng các miếng vá (bẩn: %s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "Áp dụng: %.*s"
     -
     - ## po/zh_CN.po ##
     -@@ po/zh_CN.po: msgstr "坏的索引文件 sha1 签名"
     - msgid "index uses %.4s extension, which we do not understand"
     - msgstr "索引使用不被支持的 %.4s 扩展"
     - 
     --# 	
     -+#
     - #: read-cache.c:1834
     - #, c-format
     - msgid "ignoring %.4s extension"
     -@@ po/zh_CN.po: msgstr "无法写入索引文件"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "脏索引:不能应用补丁(脏文件:%s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "应用:%.*s"
     - 
     -+#: builtin/am.c:1818
     -+#, c-format
     -+msgid "Skipping: %.*s"
     -+msgstr "跳过:%.*s"
     -+
     - #: builtin/am.c:1815
     - msgid "No changes -- Patch already applied."
     - msgstr "没有变更 —— 补丁已经应用过。"
     -
     - ## po/zh_TW.po ##
     -@@ po/zh_TW.po: msgstr "無法寫入索引檔案"
     - msgid "Dirty index: cannot apply patches (dirty: %s)"
     - msgstr "髒索引:不能套用修補檔(髒檔案:%s)"
     - 
     --#: builtin/am.c:1798 builtin/am.c:1865
     -+#: builtin/am.c:1834 builtin/am.c:1902
     - #, c-format
     - msgid "Applying: %.*s"
     - msgstr "套用:%.*s"
     - 
     -+#: builtin/am.c:1818
     -+#, c-format
     -+msgid "Skipping: %.*s"
     -+msgstr "忽略:%.*s"
     -+
     - #: builtin/am.c:1815
     - msgid "No changes -- Patch already applied."
     - msgstr "沒有變更——修補檔已經套用過。"
     -
       ## t/t4150-am.sh ##
      @@ t/t4150-am.sh: test_expect_success setup '
       
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
       	git -C client am ../patch
       '
       
     -+test_expect_success 'An empty input file is error regardless of --empty option' '
     ++test_expect_success 'an empty input file is error regardless of --empty option' '
     ++	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am --empty=drop empty.patch 2>actual &&
     -+	echo Patch format detection failed. >expected &&
     ++	echo "Patch format detection failed." >expected &&
      +	test_cmp expected actual
      +'
      +
      +test_expect_success 'invalid when passing the --empty option alone' '
     ++	test_when_finished "git am --abort || :" &&
      +	git checkout empty-commit^ &&
      +	test_must_fail git am --empty empty-commit.patch 2>err &&
      +	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +test_expect_success 'a message without a patch is an error (default)' '
      +	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am empty-commit.patch >err &&
     -+	grep "Patch is empty" err &&
     -+	rm -fr .git/rebase-apply
     ++	grep "Patch is empty" err
      +'
      +
      +test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
      +	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am --empty=die empty-commit.patch >err &&
     -+	grep "Patch is empty." err &&
     -+	rm -fr .git/rebase-apply
     ++	grep "Patch is empty." err
      +'
      +
      +test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '

-- 
gitgitgadget

[PATCH v8 2/2] am: support --empty=<option> to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 06:47:04

From: Aleen <redacted>

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: Aleen 徐沛文 <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v9 0/2] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 07:02:39

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

Aleen 徐沛文 (2):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches

 Documentation/git-am.txt           |  8 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 55 +++++++++++++++++++++++++++---
 t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 6 deletions(-)


base-commit: ca35af825273b98fc8dc11527488952f5db8eb80
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v9
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v9
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v8:

 1:  5d98a088e14 ! 1:  3b41ca3dec7 doc: git-format-patch: describe the option --always
     @@
       ## Metadata ##
     -Author: Aleen [off-list ref]
     +Author: Aleen 徐沛文 [off-list ref]
      
       ## Commit message ##
          doc: git-format-patch: describe the option --always
 2:  3ff18e16a7a ! 2:  d2ec18b36af am: support --empty=<option> to handle empty patches
     @@
       ## Metadata ##
     -Author: Aleen [off-list ref]
     +Author: Aleen 徐沛文 [off-list ref]
      
       ## Commit message ##
          am: support --empty=<option> to handle empty patches

-- 
gitgitgadget

[PATCH v9 1/2] doc: git-format-patch: describe the option --always

From: Aleen 徐沛文 via GitGitGadget <hidden>
Date: 2021-11-22 07:02:40

From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <redacted>

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: Aleen 徐沛文 <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v9 2/2] am: support --empty=<option> to handle empty patches

From: Aleen 徐沛文 via GitGitGadget <hidden>
Date: 2021-11-22 07:02:43

From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <redacted>

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: Aleen 徐沛文 <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v9 2/2] am: support --empty=<option> to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-22 07:04:28

Can somebody from GGG land help this user?  I _think_ the easiest
workaround (other than not using GGG and sending e-mail in the old
fashioned way) is to commit and sign-off under the real name, and
push under whatever GitHub username to throw a GGG pull request,
which GGG should be able to take, as I have seen users forward other
authors commits just fine.
Sorry for the wrong report, and I have checked that the DCO integrated by GGG has only checked
whether the committer name is the same as the signed name. So I have changed it, and re-submitted
it via version 9.
`--empty-commit=(die|drop|keep)::`
Besides, I have also fixed a missed mistake, which also exists in the `seen` branch.

Re: [PATCH v8 2/2] am: support --empty=<option> to handle empty patches

From: Junio C Hamano <hidden>
Date: 2021-11-22 07:06:19

"Aleen via GitGitGadget" [off-list ref] writes:
From: Aleen <redacted>

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: Aleen 徐沛文 <redacted>
Perhaps this line should imitate what Hans Krentel did in
https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com/,
i.e. real name first, and then (nickname) in parentheses.

Also, the in-body "From:" line should match the sign off.

I corrected what has been queued in 'seen' manually when I applied
the previous round.
+--empty=(die|drop|keep)::
This is the only change relative to what is queued (we had
"--empty-commit", which is remnant from an earlier iteration), and
it makes the documentation consistent with what the code does.

Good.

Re: [PATCH v8 2/2] am: support --empty=<option> to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-22 07:19:23

Perhaps this line should imitate what Hans Krentel did in
https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com/,
i.e. real name first, and then (nickname) in parentheses.

Also, the in-body "From:" line should match the sign off.

I corrected what has been queued in 'seen' manually when I applied
the previous round.
quoted
+--empty=(die|drop|keep)::
This is the only change relative to what is queued (we had
"--empty-commit", which is remnant from an earlier iteration), and
it makes the documentation consistent with what the code does.

Good.
It seems there is something wrong when submitting via "徐沛文 (Aleen) [off-list ref]".

[PATCH v10 1/2] doc: git-format-patch: describe the option --always

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 07:51:21

From: Aleen <redacted>

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v10 0/2] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 07:51:22

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

cc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:
Aleen 徐沛文 pwxu@coremail.cn

Aleen (2):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches

 Documentation/git-am.txt           |  8 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 55 +++++++++++++++++++++++++++---
 t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 6 deletions(-)


base-commit: ca35af825273b98fc8dc11527488952f5db8eb80
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v10
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v9:

 1:  3b41ca3dec7 ! 1:  59bce7131da doc: git-format-patch: describe the option --always
     @@
       ## Metadata ##
     -Author: Aleen 徐沛文 [off-list ref]
     +Author: Aleen [off-list ref]
      
       ## Commit message ##
          doc: git-format-patch: describe the option --always
     @@ Commit message
          This commit has described how to use '--always' option in the command
          'git-format-patch' to include patches for commits that emit no changes.
      
     -    Signed-off-by: Aleen 徐沛文 [off-list ref]
     +    Signed-off-by: 徐沛文 (Aleen) [off-list ref]
      
       ## Documentation/git-format-patch.txt ##
      @@ Documentation/git-format-patch.txt: SYNOPSIS
 2:  d2ec18b36af ! 2:  5025ad30ba7 am: support --empty=<option> to handle empty patches
     @@
       ## Metadata ##
     -Author: Aleen 徐沛文 [off-list ref]
     +Author: Aleen [off-list ref]
      
       ## Commit message ##
          am: support --empty=<option> to handle empty patches
     @@ Commit message
          those empty patches. In this commit, we have implemented three
          valid options ('die', 'drop' and 'keep').
      
     -    Signed-off-by: Aleen 徐沛文 [off-list ref]
     +    Signed-off-by: 徐沛文 (Aleen) [off-list ref]
      
       ## Documentation/git-am.txt ##
      @@ Documentation/git-am.txt: SYNOPSIS

-- 
gitgitgadget

[PATCH v10 2/2] am: support --empty=<option> to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-22 07:51:25

From: Aleen <redacted>

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always

From: Johannes Schindelin <hidden>
Date: 2021-11-22 12:00:48

Hi Aleen,

On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
From: Aleen <redacted>
FWIW this information comes from your commit, specifically from the
author information recorded when you committed first. To re-set it in
these two patches, run something like this:

	git config --global user.name "徐沛文 (Aleen)"
	git rebase -x "git commit --amend --no-edit --reset-author" HEAD~2

and then force-push to your branch.

Ciao,
Dscho

Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always

From: Aleen 徐沛文 <hidden>
Date: 2021-11-23 01:25:35

Hi Aleen,

On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
quoted
From: Aleen <redacted>
FWIW this information comes from your commit, specifically from the
author information recorded when you committed first. To re-set it in
these two patches, run something like this:

	git config --global user.name "徐沛文 (Aleen)"
	git rebase -x "git commit --amend --no-edit --reset-author" HEAD~2

and then force-push to your branch.

Ciao,
Dscho
The main problem is that it broke when I tried to submit via GGG:
https://github.com/gitgitgadget/git/pull/1076#issuecomment-976087421

Re: [PATCH v10 1/2] doc: git-format-patch: describe the option --always

From: Johannes Schindelin <hidden>
Date: 2021-11-23 12:30:31

Hi Aleen,

On Tue, 23 Nov 2021, Aleen 徐沛文 wrote:
quoted
Hi Aleen,

On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
quoted
From: Aleen <redacted>
FWIW this information comes from your commit, specifically from the
author information recorded when you committed first. To re-set it in
these two patches, run something like this:

	git config --global user.name "徐沛文 (Aleen)"
	git rebase -x "git commit --amend --no-edit --reset-author" HEAD~2

and then force-push to your branch.

Ciao,
Dscho
The main problem is that it broke when I tried to submit via GGG:
https://github.com/gitgitgadget/git/pull/1076#issuecomment-976087421
Whoops. It seems that the `From:` header cannot be parsed by GitGitGadget.
Most likely because the `<` and `>` of the email address are on the next
line, not the same line as the `From:`.

I had a quick look and opened
https://github.com/gitgitgadget/gitgitgadget/pull/777 to fix this.

Ciao,
Dscho

[PATCH v11 0/2] am: support --empty=(die|drop|keep) option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-23 15:27:00

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

cc: René Scharfe l.s.r@web.de cc: Phillip Wood phillip.wood123@gmail.com cc:
Aleen 徐沛文 pwxu@coremail.cn

cc: Aleen 徐沛文 pwxu@coremail.cn

徐沛文 (Aleen) (2):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches

 Documentation/git-am.txt           |  8 +++++
 Documentation/git-format-patch.txt |  6 +++-
 builtin/am.c                       | 55 +++++++++++++++++++++++++++---
 t/t4150-am.sh                      | 49 ++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 6 deletions(-)


base-commit: f8b28837226f3932b867ca88a4f830bf203d2afe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v11
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v11
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v10:

 1:  59bce7131da ! 1:  3d7e96ce2b3 doc: git-format-patch: describe the option --always
     @@
       ## Metadata ##
     -Author: Aleen [off-list ref]
     +Author: 徐沛文 (Aleen) [off-list ref]
      
       ## Commit message ##
          doc: git-format-patch: describe the option --always
 2:  5025ad30ba7 ! 2:  6051ad9440a am: support --empty=<option> to handle empty patches
     @@
       ## Metadata ##
     -Author: Aleen [off-list ref]
     +Author: 徐沛文 (Aleen) [off-list ref]
      
       ## Commit message ##
          am: support --empty=<option> to handle empty patches

-- 
gitgitgadget

[PATCH v11 1/2] doc: git-format-patch: describe the option --always

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-23 15:27:03

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v11 2/2] am: support --empty=<option> to handle empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-23 15:27:04

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

Re: [PATCH v11 1/2] doc: git-format-patch: describe the option --always

From: Johannes Schindelin <hidden>
Date: 2021-11-23 16:12:17

Hi,

On Tue, 23 Nov 2021, 徐沛文 (Aleen) via GitGitGadget wrote:
From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]
I triggered the Azure Pipeline to submit this patch series after merging
the PR that fixed parsing of the `From:` line.

It looks a bit funny that this is in two lines, so I downloaded the mail
from https://lore.kernel.org/git/ and fed it to `git am`, which was just
fine with it.

Ciao,
Dscho

Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches

From: Elijah Newren <hidden>
Date: 2021-11-26 20:19:10

On Tue, Nov 23, 2021 at 8:38 AM 徐沛文 (Aleen) via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
         [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
         [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
         [--quoted-cr=<action>]
+        [--empty=(die|drop|keep)]
         [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
        This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).

+--empty=(die|drop|keep)::
+       By default, or when the option is set to 'die', the command
+       errors out on an input e-mail message that lacks a patch. When
+       this option is set to 'drop', skip such an e-mail message instead.
+       When this option is set to 'keep', create an empty commit,
+       recording the contents of the e-mail message as its log.
What does 'errors out' mean?  Is the am operation aborted, and the
user return to the pre-am state?  Or is the am operation interrupted,
with the user being asked to choose whether to keep or drop the patch?
 Or something else (my first thought was "Are you going to leave the
index locked?")?  This description is not that clear.  To me, the
wording suggests aborted (or worse), but what you actually implemented
was an interrupt-and-ask.

Can I suggest using 'ask' instead of 'die'?  I think that will be
clearer, and it matches the term used by git rebase --empty.

Also, the only instructions given to the user when you interrupt
include how to skip the patch, but I don't see anything for how to
keep it.  The instructions are:
'''
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
'''

I tried it manually, and it turns out "git am --continue" will just
spit out basically the same message again:
'''
Applying: empty commit
No changes - did you forget to use 'git add'?
If there is nothing left to stage, chances are that something else
already introduced the same changes; you might want to skip this patch.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
'''

And if I try to run `git commit --allow-empty` (which I happen to
remember is the command suggested by `git rebase --empty=ask` when it
stops), then I'm given an empty editor; it is not pre-populated with
the appropriate commit message.  Can the portion of the empty patch
corresponding to the commit message be added to .git/COMMIT_EDITMSG to
correct that?  Also, can some extra words be printed before
interrupting to explain what to do when you want to keep the empty
commit?  Something like:
"""
The current commit being applied is empty.  If you wish to commit it
anyway, use:
    git commit --allow-empty
"""
quoted hunk
+
 -m::
 --message-id::
        Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
        SHOW_PATCH_DIFF = 1,
 };

+enum empty_action {
+       DIE_EMPTY_COMMIT = 0,  /* output errors */
+       DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+       KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
        /* state directory path */
        char *dir;
@@ -118,6 +124,7 @@ struct am_state {
        int message_id;
        int scissors; /* enum scissors_type */
        int quoted_cr; /* enum quoted_cr_action */
+       int empty_type; /* enum empty_action */
        struct strvec git_apply_opts;
        const char *resolvemsg;
        int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
        return 0;
 }

+static int am_option_parse_empty(const struct option *opt,
+                                    const char *arg, int unset)
+{
+       int *opt_value = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+
+       if (!strcmp(arg, "die"))
+               *opt_value = DIE_EMPTY_COMMIT;
+       else if (!strcmp(arg, "drop"))
+               *opt_value = DROP_EMPTY_COMMIT;
+       else if (!strcmp(arg, "keep"))
+               *opt_value = KEEP_EMPTY_COMMIT;
+       else
+               return error(_("Invalid value for --empty: %s"), arg);
+
+       return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
                goto finish;
        }

-       if (is_empty_or_missing_file(am_path(state, "patch"))) {
-               printf_ln(_("Patch is empty."));
-               die_user_resolve(state);
-       }
-
        strbuf_addstr(&msg, "\n\n");
        strbuf_addbuf(&msg, &mi.log_message);
        strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
        while (state->cur <= state->last) {
                const char *mail = am_path(state, msgnum(state));
                int apply_status;
+               int to_keep;

                reset_ident_date();
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
                if (state->interactive && do_interactive(state))
                        goto next;

+               to_keep = 0;
+               if (is_empty_or_missing_file(am_path(state, "patch"))) {
+                       switch (state->empty_type) {
+                       case DROP_EMPTY_COMMIT:
+                               say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+                               goto next;
+                               break;
+                       case KEEP_EMPTY_COMMIT:
+                               to_keep = 1;
+                               break;
+                       case DIE_EMPTY_COMMIT:
+                               printf_ln(_("Patch is empty."));
+                               die_user_resolve(state);
+                               break;
+                       }
+               }
+
                if (run_applypatch_msg_hook(state))
                        exit(1);
+               if (to_keep)
+                       goto commit;

                say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
                        die_user_resolve(state);
                }

+commit:
                do_commit(state);

 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
                  N_("GPG-sign commits"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+               OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+                 N_("how to handle empty patches"),
+                 PARSE_OPT_NONEG, am_option_parse_empty),
                OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
                        N_("(internal use for git-rebase)")),
                OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '

        git format-patch -M --stdout lorem^ >rename-add.patch &&

+       git checkout -b empty-commit &&
+       git commit -m "empty commit" --allow-empty &&
+
+       : >empty.patch &&
+       git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
        # reset time
        sane_unset test_tick &&
        test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
        git -C client am ../patch
 '

+test_expect_success 'an empty input file is error regardless of --empty option' '
+       test_when_finished "git am --abort || :" &&
+       test_must_fail git am --empty=drop empty.patch 2>actual &&
+       echo "Patch format detection failed." >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+       test_when_finished "git am --abort || :" &&
+       git checkout empty-commit^ &&
+       test_must_fail git am --empty empty-commit.patch 2>err &&
+       echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+       test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+       test_when_finished "git am --abort || :" &&
+       test_must_fail git am empty-commit.patch >err &&
+       grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+       test_when_finished "git am --abort || :" &&
+       test_must_fail git am --empty=die empty-commit.patch >err &&
+       grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+       git am --empty=drop empty-commit.patch >output &&
+       git rev-parse empty-commit^ >expected &&
+       git rev-parse HEAD >actual &&
+       test_cmp expected actual &&
+       grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+       git am --empty=keep empty-commit.patch &&
+       test_path_is_missing .git/rebase-apply &&
+       git show empty-commit --format="%s" >expected &&
+       git show HEAD --format="%s" >actual &&
+       test_cmp actual expected
+'
+
 test_done
--
gitgitgadget

Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-29 09:21:35

Dears Hamano,

   Elijah Newren has given two better suggestions:

       1. Use 'ask' rather than 'die'
       2. When erroring out 'Patch is empty', print out a tutorial information
          to help users using 'git commit --allow-empty' to keep recording as 
          an empty commit.

   Should we continue to implement these features in current PR?

Aleen
quoted
+--empty=(die|drop|keep)::
+       By default, or when the option is set to 'die', the command
+       errors out on an input e-mail message that lacks a patch. When
+       this option is set to 'drop', skip such an e-mail message instead.
+       When this option is set to 'keep', create an empty commit,
+       recording the contents of the e-mail message as its log.
What does 'errors out' mean?  Is the am operation aborted, and the
user return to the pre-am state?  Or is the am operation interrupted,
with the user being asked to choose whether to keep or drop the patch?
 Or something else (my first thought was "Are you going to leave the
index locked?")?  This description is not that clear.  To me, the
wording suggests aborted (or worse), but what you actually implemented
was an interrupt-and-ask.

Can I suggest using 'ask' instead of 'die'?  I think that will be
clearer, and it matches the term used by git rebase --empty.

Also, the only instructions given to the user when you interrupt
include how to skip the patch, but I don't see anything for how to
keep it.  The instructions are:
'''
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
'''

I tried it manually, and it turns out "git am --continue" will just
spit out basically the same message again:
'''
Applying: empty commit
No changes - did you forget to use 'git add'?
If there is nothing left to stage, chances are that something else
already introduced the same changes; you might want to skip this patch.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
'''

And if I try to run `git commit --allow-empty` (which I happen to
remember is the command suggested by `git rebase --empty=ask` when it
stops), then I'm given an empty editor; it is not pre-populated with
the appropriate commit message.  Can the portion of the empty patch
corresponding to the commit message be added to .git/COMMIT_EDITMSG to
correct that?  Also, can some extra words be printed before
interrupting to explain what to do when you want to keep the empty
commit?  Something like:
"""
The current commit being applied is empty.  If you wish to commit it
anyway, use:
    git commit --allow-empty
"""

Re: Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-29 10:02:32

It is quite complicated to support 'git commit --allow-empty' extracting messages
from an empty patch, because 'git am' has to find somewhere to store the parsed state
for 'git commit' to read. How about directly supporting another interactive option
'--allow-empty' for 'git am' to keep recording?
Dears Hamano,

   Elijah Newren has given two better suggestions:

       1. Use 'ask' rather than 'die'
       2. When erroring out 'Patch is empty', print out a tutorial information
          to help users using 'git commit --allow-empty' to keep recording as 
          an empty commit.

   Should we continue to implement these features in current PR?

Aleen

Re: Re: [PATCH v11 2/2] am: support --empty=<option> to handle empty patches

From: Elijah Newren <hidden>
Date: 2021-11-29 20:35:56

On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 [off-list ref] wrote:
It is quite complicated to support 'git commit --allow-empty' extracting messages
from an empty patch, because 'git am' has to find somewhere to store the parsed state
for 'git commit' to read.
.git/COMMIT_EDITMSG is such a place that already exists, assuming you
are just extracting a commit message.
How about directly supporting another interactive option
'--allow-empty' for 'git am' to keep recording?
--empty=keep was already part of your patch series; I don't see why
you'd need to add a synonym (--allow-empty) for it.

I think you were trying to ask if you could just leave out the
implementation of --empty=ask (or --empty=die) from your patches.
That's usually fine, but there is a small wrinkle here since it was
your chosen default.  You'll have to pick a different default, and
possibly alert users in the documentation that the default may change
in the future if/when the other option is implemented.
quoted
Dears Hamano,

   Elijah Newren has given two better suggestions:

       1. Use 'ask' rather than 'die'
       2. When erroring out 'Patch is empty', print out a tutorial information
          to help users using 'git commit --allow-empty' to keep recording as
          an empty commit.

   Should we continue to implement these features in current PR?

Aleen

[PATCH v12 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-30 05:37:30

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

----------------------------------------------------------------------------

Changes since v11:

 1. introduce an interactive option --allow-empty for git-am to record empty
    patches in the middle of an am session.

徐沛文 (Aleen) (3):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches
  am: support --allow-empty to record specific empty patches

 Documentation/git-am.txt           | 14 +++++-
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 77 ++++++++++++++++++++++++++----
 t/t4150-am.sh                      | 61 +++++++++++++++++++++++
 t/t7512-status-help.sh             |  1 +
 wt-status.c                        |  3 ++
 6 files changed, 151 insertions(+), 11 deletions(-)


base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v12
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v12
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v11:

 1:  3d7e96ce2b3 = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  6051ad9440a = 2:  a3e850bab7d am: support --empty=<option> to handle empty patches
 -:  ----------- > 3:  d44dac09c87 am: support --allow-empty to record specific empty patches

-- 
gitgitgadget

[PATCH v12 1/3] doc: git-format-patch: describe the option --always

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 05:37:31

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v12 2/3] am: support --empty=<option> to handle empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 05:37:35

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v12 3/3] am: support --allow-empty to record specific empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 05:37:36

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This option helps to record specific empty patches in the middle
of an am session.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  6 +++++-
 builtin/am.c             | 24 +++++++++++++++++++-----
 t/t4150-am.sh            | 12 ++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index ba17063f621..fe3af32f7f7 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)
 
 DESCRIPTION
 -----------
@@ -199,6 +199,10 @@ default.   You can use `--no-utf8` to override this.
 	the e-mail message; if `diff`, show the diff portion only.
 	Defaults to `raw`.
 
+--allow-empty::
+	Keep recording the empty patch as an empty commit with
+	the contents of the e-mail message as its log.
+
 DISCUSSION
 ----------
 
diff --git a/builtin/am.c b/builtin/am.c
index cc6512275aa..2ae6fabb28a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)
 				to_keep = 1;
 				break;
 			case DIE_EMPTY_COMMIT:
-				printf_ln(_("Patch is empty."));
+				printf_ln(_("Patch is empty.\n"
+							"If you want to keep recording it, run \"git am --allow-empty\"."));
 				die_user_resolve(state);
 				break;
 			}
@@ -1898,10 +1899,15 @@ next:
 /**
  * Resume the current am session after patch application failure. The user did
  * all the hard work, and we do not have to do any patch application. Just
- * trust and commit what the user has in the index and working tree.
+ * trust and commit what the user has in the index and working tree. If `allow_empty`
+ * is true, commit as an empty commit.
  */
-static void am_resolve(struct am_state *state)
+static void am_resolve(struct am_state *state, int allow_empty)
 {
+	if (allow_empty) {
+		goto commit;
+	}
+
 	validate_resume_state(state);
 
 	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
@@ -1928,6 +1934,7 @@ static void am_resolve(struct am_state *state)
 
 	repo_rerere(the_repository, 0);
 
+commit:
 	do_commit(state);
 
 next:
@@ -2237,7 +2244,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2390,6 +2398,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		  N_("show the patch being applied"),
 		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
 		  parse_opt_show_current_patch, RESUME_SHOW_PATCH },
+		OPT_CMDMODE(0, "allow-empty", &resume.mode,
+			N_("keep recording the empty patch as empty commits"),
+			RESUME_ALLOW_EMPTY),
 		OPT_BOOL(0, "committer-date-is-author-date",
 			&state.committer_date_is_author_date,
 			N_("lie about committer date")),
@@ -2498,7 +2509,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		am_run(&state, 1);
 		break;
 	case RESUME_RESOLVED:
-		am_resolve(&state);
+		am_resolve(&state, 0);
+		break;
+	case RESUME_ALLOW_EMPTY:
+		am_resolve(&state, 1);
 		break;
 	case RESUME_SKIP:
 		am_skip(&state);
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 8c8bd4db220..cab21411f00 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1201,4 +1201,16 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
 	test_cmp actual expected
 '
 
+
+test_expect_success 'record as an empty commit in the middle of an am session' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	grep "If you want to keep recording it, run \"git am --allow-empty\"." err &&
+	git am --allow-empty &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 7f2956d77ad..5513018154e 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '
 On branch am_empty
 You are in the middle of an am session.
 The current patch is empty.
+  (use "git am --allow-empty" to keep recording this empty patch)
   (use "git am --skip" to skip this patch)
   (use "git am --abort" to restore the original branch)
 
diff --git a/wt-status.c b/wt-status.c
index 5d215f4e4f1..d036f88a6b9 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,
 		if (!s->state.am_empty_patch)
 			status_printf_ln(s, color,
 				_("  (fix conflicts and then run \"git am --continue\")"));
+		else
+			status_printf_ln(s, color,
+				_("  (use \"git am --allow-empty\" to keep recording this empty patch)"));
 		status_printf_ln(s, color,
 			_("  (use \"git am --skip\" to skip this patch)"));
 		status_printf_ln(s, color,
-- 
gitgitgadget

Re: [PATCH v12 3/3] am: support --allow-empty to record specific empty patches

From: Aleen 徐沛文 <hidden>
Date: 2021-11-30 05:45:14

I have introduced an interactive option `--allow-empty` in a new independent commit,
and it is not the synonym of the `--empty` option because it is only for the case in
a middle am session where users can resolve "died" problems by choosing whether to
record current empty patch. The `--empty` option acts like a strategy option to tell
`git-am` how to handle when meeting any empty patches.
On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 [off-list ref] wrote:
quoted
It is quite complicated to support 'git commit --allow-empty' extracting messages
from an empty patch, because 'git am' has to find somewhere to store the parsed state
for 'git commit' to read.
.git/COMMIT_EDITMSG is such a place that already exists, assuming you
are just extracting a commit message.
quoted
How about directly supporting another interactive option
'--allow-empty' for 'git am' to keep recording?
--empty=keep was already part of your patch series; I don't see why
you'd need to add a synonym (--allow-empty) for it.

I think you were trying to ask if you could just leave out the
implementation of --empty=ask (or --empty=die) from your patches.
That's usually fine, but there is a small wrinkle here since it was
your chosen default.  You'll have to pick a different default, and
possibly alert users in the documentation that the default may change
in the future if/when the other option is implemented.
quoted
quoted
Dears Hamano,

   Elijah Newren has given two better suggestions:

       1. Use 'ask' rather than 'die'
       2. When erroring out 'Patch is empty', print out a tutorial information
          to help users using 'git commit --allow-empty' to keep recording as
          an empty commit.

   Should we continue to implement these features in current PR?

Aleen

[PATCH v13 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-11-30 09:55:20

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

----------------------------------------------------------------------------

Changes since v11:

 1. introduce an interactive option --allow-empty for git-am to record empty
    patches in the middle of an am session.

----------------------------------------------------------------------------

Changes since v12:

 1. record the empty patch as an empty commit only when there are no
    changes.
 2. fix indentation problems.
 3. simplify "to keep recording" to "to record".
 4. add a test case for skipping empty patches via the --skip option.

徐沛文 (Aleen) (3):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches
  am: support --allow-empty to record specific empty patches

 Documentation/git-am.txt           | 14 ++++-
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 82 +++++++++++++++++++++++++-----
 t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++
 t/t7512-status-help.sh             |  1 +
 wt-status.c                        |  3 ++
 6 files changed, 164 insertions(+), 15 deletions(-)


base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v13
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v13
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v12:

 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  a3e850bab7d = 2:  a3e850bab7d am: support --empty=<option> to handle empty patches
 3:  d44dac09c87 ! 3:  08bd397ae7a am: support --allow-empty to record specific empty patches
     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.
       	Defaults to `raw`.
       
      +--allow-empty::
     -+	Keep recording the empty patch as an empty commit with
     ++	Record the empty patch as an empty commit with
      +	the contents of the e-mail message as its log.
      +
       DISCUSSION
     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
       			case DIE_EMPTY_COMMIT:
      -				printf_ln(_("Patch is empty."));
      +				printf_ln(_("Patch is empty.\n"
     -+							"If you want to keep recording it, run \"git am --allow-empty\"."));
     ++					    "If you want to record it as an empty commit, run \"git am --allow-empty\"."));
       				die_user_resolve(state);
       				break;
       			}
     @@ builtin/am.c: next:
        * all the hard work, and we do not have to do any patch application. Just
      - * trust and commit what the user has in the index and working tree.
      + * trust and commit what the user has in the index and working tree. If `allow_empty`
     -+ * is true, commit as an empty commit.
     ++ * is true, commit as an empty commit when there is no changes.
        */
      -static void am_resolve(struct am_state *state)
      +static void am_resolve(struct am_state *state, int allow_empty)
       {
     -+	if (allow_empty) {
     -+		goto commit;
     -+	}
     -+
       	validate_resume_state(state);
       
       	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
     -@@ builtin/am.c: static void am_resolve(struct am_state *state)
     - 
     - 	repo_rerere(the_repository, 0);
       
     -+commit:
     - 	do_commit(state);
     + 	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
     +-		printf_ln(_("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."));
     +-		die_user_resolve(state);
     ++		if (allow_empty)
     ++			printf_ln(_("No changes - record it as an empty commit."));
     ++		else {
     ++			printf_ln(_("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."));
     ++			die_user_resolve(state);
     ++		}
     + 	}
       
     - next:
     + 	if (unmerged_cache()) {
      @@ builtin/am.c: enum resume_type {
       	RESUME_SKIP,
       	RESUME_ABORT,
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
       		  parse_opt_show_current_patch, RESUME_SHOW_PATCH },
      +		OPT_CMDMODE(0, "allow-empty", &resume.mode,
     -+			N_("keep recording the empty patch as empty commits"),
     ++			N_("record the empty patch as an empty commit"),
      +			RESUME_ALLOW_EMPTY),
       		OPT_BOOL(0, "committer-date-is-author-date",
       			&state.committer_date_is_author_date,
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		break;
       	case RESUME_RESOLVED:
      -		am_resolve(&state);
     -+		am_resolve(&state, 0);
     -+		break;
      +	case RESUME_ALLOW_EMPTY:
     -+		am_resolve(&state, 1);
     ++		am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);
       		break;
       	case RESUME_SKIP:
       		am_skip(&state);
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
       	test_cmp actual expected
       '
       
     ++test_expect_success 'skip an empty patch in the middle of an am session' '
     ++	git checkout empty-commit^ &&
     ++	test_must_fail git am empty-commit.patch >err &&
     ++	grep "Patch is empty." err &&
     ++	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
     ++	git am --skip &&
     ++	test_path_is_missing .git/rebase-apply &&
     ++	git rev-parse empty-commit^ >expected &&
     ++	git rev-parse HEAD >actual &&
     ++	test_cmp expected actual
     ++'
      +
     -+test_expect_success 'record as an empty commit in the middle of an am session' '
     ++test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '
      +	git checkout empty-commit^ &&
      +	test_must_fail git am empty-commit.patch >err &&
      +	grep "Patch is empty." err &&
     -+	grep "If you want to keep recording it, run \"git am --allow-empty\"." err &&
     ++	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
      +	git am --allow-empty &&
     ++	test_path_is_missing .git/rebase-apply &&
      +	git show empty-commit --format="%s" >expected &&
      +	git show HEAD --format="%s" >actual &&
      +	test_cmp actual expected
     @@ t/t7512-status-help.sh: test_expect_success 'status in an am session: empty patc
       On branch am_empty
       You are in the middle of an am session.
       The current patch is empty.
     -+  (use "git am --allow-empty" to keep recording this empty patch)
     ++  (use "git am --allow-empty" to record this patch as an empty commit)
         (use "git am --skip" to skip this patch)
         (use "git am --abort" to restore the original branch)
       
     @@ wt-status.c: static void show_am_in_progress(struct wt_status *s,
       				_("  (fix conflicts and then run \"git am --continue\")"));
      +		else
      +			status_printf_ln(s, color,
     -+				_("  (use \"git am --allow-empty\" to keep recording this empty patch)"));
     ++				_("  (use \"git am --allow-empty\" to record this patch as an empty commit)"));
       		status_printf_ln(s, color,
       			_("  (use \"git am --skip\" to skip this patch)"));
       		status_printf_ln(s, color,

-- 
gitgitgadget

[PATCH v13 1/3] doc: git-format-patch: describe the option --always

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 09:55:26

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v13 2/3] am: support --empty=<option> to handle empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 09:55:27

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..ba17063f621 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,13 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message that lacks a patch. When
+	this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v13 3/3] am: support --allow-empty to record specific empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-11-30 09:55:30

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This option helps to record specific empty patches in the middle
of an am session.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  6 +++++-
 builtin/am.c             | 29 ++++++++++++++++++++---------
 t/t4150-am.sh            | 24 ++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index ba17063f621..f2c5e3e37e0 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)
 
 DESCRIPTION
 -----------
@@ -199,6 +199,10 @@ default.   You can use `--no-utf8` to override this.
 	the e-mail message; if `diff`, show the diff portion only.
 	Defaults to `raw`.
 
+--allow-empty::
+	Record the empty patch as an empty commit with
+	the contents of the e-mail message as its log.
+
 DISCUSSION
 ----------
 
diff --git a/builtin/am.c b/builtin/am.c
index cc6512275aa..58c7638b9bc 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)
 				to_keep = 1;
 				break;
 			case DIE_EMPTY_COMMIT:
-				printf_ln(_("Patch is empty."));
+				printf_ln(_("Patch is empty.\n"
+					    "If you want to record it as an empty commit, run \"git am --allow-empty\"."));
 				die_user_resolve(state);
 				break;
 			}
@@ -1898,19 +1899,24 @@ next:
 /**
  * Resume the current am session after patch application failure. The user did
  * all the hard work, and we do not have to do any patch application. Just
- * trust and commit what the user has in the index and working tree.
+ * trust and commit what the user has in the index and working tree. If `allow_empty`
+ * is true, commit as an empty commit when there is no changes.
  */
-static void am_resolve(struct am_state *state)
+static void am_resolve(struct am_state *state, int allow_empty)
 {
 	validate_resume_state(state);
 
 	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
 	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
-		printf_ln(_("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."));
-		die_user_resolve(state);
+		if (allow_empty)
+			printf_ln(_("No changes - record it as an empty commit."));
+		else {
+			printf_ln(_("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."));
+			die_user_resolve(state);
+		}
 	}
 
 	if (unmerged_cache()) {
@@ -2237,7 +2243,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2390,6 +2397,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		  N_("show the patch being applied"),
 		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
 		  parse_opt_show_current_patch, RESUME_SHOW_PATCH },
+		OPT_CMDMODE(0, "allow-empty", &resume.mode,
+			N_("record the empty patch as an empty commit"),
+			RESUME_ALLOW_EMPTY),
 		OPT_BOOL(0, "committer-date-is-author-date",
 			&state.committer_date_is_author_date,
 			N_("lie about committer date")),
@@ -2498,7 +2508,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		am_run(&state, 1);
 		break;
 	case RESUME_RESOLVED:
-		am_resolve(&state);
+	case RESUME_ALLOW_EMPTY:
+		am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);
 		break;
 	case RESUME_SKIP:
 		am_skip(&state);
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 8c8bd4db220..3e60460022f 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1201,4 +1201,28 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
 	test_cmp actual expected
 '
 
+test_expect_success 'skip an empty patch in the middle of an am session' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --skip &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --allow-empty &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 7f2956d77ad..9309becfe03 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '
 On branch am_empty
 You are in the middle of an am session.
 The current patch is empty.
+  (use "git am --allow-empty" to record this patch as an empty commit)
   (use "git am --skip" to skip this patch)
   (use "git am --abort" to restore the original branch)
 
diff --git a/wt-status.c b/wt-status.c
index 5d215f4e4f1..d578a0e9192 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,
 		if (!s->state.am_empty_patch)
 			status_printf_ln(s, color,
 				_("  (fix conflicts and then run \"git am --continue\")"));
+		else
+			status_printf_ln(s, color,
+				_("  (use \"git am --allow-empty\" to record this patch as an empty commit)"));
 		status_printf_ln(s, color,
 			_("  (use \"git am --skip\" to skip this patch)"));
 		status_printf_ln(s, color,
-- 
gitgitgadget

[PATCH v14 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-12-01 03:37:47

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

----------------------------------------------------------------------------

Changes since v11:

 1. introduce an interactive option --allow-empty for git-am to record empty
    patches in the middle of an am session.

----------------------------------------------------------------------------

Changes since v12:

 1. record the empty patch as an empty commit only when there are no
    changes.
 2. fix indentation problems.
 3. simplify "to keep recording" to "to record".
 4. add a test case for skipping empty patches via the --skip option.

----------------------------------------------------------------------------

Changes since v13:

 1. add an additional description about the 'die' value.

徐沛文 (Aleen) (3):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches
  am: support --allow-empty to record specific empty patches

 Documentation/git-am.txt           | 16 +++++-
 Documentation/git-format-patch.txt |  6 ++-
 builtin/am.c                       | 82 +++++++++++++++++++++++++-----
 t/t4150-am.sh                      | 73 ++++++++++++++++++++++++++
 t/t7512-status-help.sh             |  1 +
 wt-status.c                        |  3 ++
 6 files changed, 166 insertions(+), 15 deletions(-)


base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v14
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v14
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v13:

 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  a3e850bab7d ! 2:  b6a04fc12df am: support --empty=<option> to handle empty patches
     @@ Documentation/git-am.txt: OPTIONS
       
      +--empty=(die|drop|keep)::
      +	By default, or when the option is set to 'die', the command
     -+	errors out on an input e-mail message that lacks a patch. When
     -+	this option is set to 'drop', skip such an e-mail message instead.
     ++	errors out on an input e-mail message lacking a patch
     ++	and stops into the middle of the current am session. When this
     ++	option is set to 'drop', skip such an e-mail message instead.
      +	When this option is set to 'keep', create an empty commit,
      +	recording the contents of the e-mail message as its log.
      +
 3:  08bd397ae7a ! 3:  cbd822d4340 am: support --allow-empty to record specific empty patches
     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.
       	Defaults to `raw`.
       
      +--allow-empty::
     -+	Record the empty patch as an empty commit with
     ++	After a patch failure on an input e-mail message lacking a patch,
     ++	the user can still record the empty patch as an empty commit with
      +	the contents of the e-mail message as its log.
      +
       DISCUSSION

-- 
gitgitgadget

[PATCH v14 1/3] doc: git-format-patch: describe the option --always

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-12-01 03:37:51

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v14 2/3] am: support --empty=<option> to handle empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-12-01 03:37:56

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..cf9cace9678 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, or when the option is set to 'die', the command
+	errors out on an input e-mail message lacking a patch
+	and stops into the middle of the current am session. When this
+	option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..cc6512275aa 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	DIE_EMPTY_COMMIT = 0,  /* output errors */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +124,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +185,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1274,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1784,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1814,27 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1868,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2399,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..8c8bd4db220 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,47 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=die empty-commit.patch >err &&
+	grep "Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget

[PATCH v14 3/3] am: support --allow-empty to record specific empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-12-01 03:38:03

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This option helps to record specific empty patches in the middle
of an am session.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  7 ++++++-
 builtin/am.c             | 29 ++++++++++++++++++++---------
 t/t4150-am.sh            | 24 ++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 54 insertions(+), 10 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index cf9cace9678..b6c065b3f7a 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
-'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
+'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)
 
 DESCRIPTION
 -----------
@@ -200,6 +200,11 @@ default.   You can use `--no-utf8` to override this.
 	the e-mail message; if `diff`, show the diff portion only.
 	Defaults to `raw`.
 
+--allow-empty::
+	After a patch failure on an input e-mail message lacking a patch,
+	the user can still record the empty patch as an empty commit with
+	the contents of the e-mail message as its log.
+
 DISCUSSION
 ----------
 
diff --git a/builtin/am.c b/builtin/am.c
index cc6512275aa..58c7638b9bc 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1825,7 +1825,8 @@ static void am_run(struct am_state *state, int resume)
 				to_keep = 1;
 				break;
 			case DIE_EMPTY_COMMIT:
-				printf_ln(_("Patch is empty."));
+				printf_ln(_("Patch is empty.\n"
+					    "If you want to record it as an empty commit, run \"git am --allow-empty\"."));
 				die_user_resolve(state);
 				break;
 			}
@@ -1898,19 +1899,24 @@ next:
 /**
  * Resume the current am session after patch application failure. The user did
  * all the hard work, and we do not have to do any patch application. Just
- * trust and commit what the user has in the index and working tree.
+ * trust and commit what the user has in the index and working tree. If `allow_empty`
+ * is true, commit as an empty commit when there is no changes.
  */
-static void am_resolve(struct am_state *state)
+static void am_resolve(struct am_state *state, int allow_empty)
 {
 	validate_resume_state(state);
 
 	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
 	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
-		printf_ln(_("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."));
-		die_user_resolve(state);
+		if (allow_empty)
+			printf_ln(_("No changes - record it as an empty commit."));
+		else {
+			printf_ln(_("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."));
+			die_user_resolve(state);
+		}
 	}
 
 	if (unmerged_cache()) {
@@ -2237,7 +2243,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2390,6 +2397,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		  N_("show the patch being applied"),
 		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
 		  parse_opt_show_current_patch, RESUME_SHOW_PATCH },
+		OPT_CMDMODE(0, "allow-empty", &resume.mode,
+			N_("record the empty patch as an empty commit"),
+			RESUME_ALLOW_EMPTY),
 		OPT_BOOL(0, "committer-date-is-author-date",
 			&state.committer_date_is_author_date,
 			N_("lie about committer date")),
@@ -2498,7 +2508,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		am_run(&state, 1);
 		break;
 	case RESUME_RESOLVED:
-		am_resolve(&state);
+	case RESUME_ALLOW_EMPTY:
+		am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);
 		break;
 	case RESUME_SKIP:
 		am_skip(&state);
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 8c8bd4db220..3e60460022f 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1201,4 +1201,28 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
 	test_cmp actual expected
 '
 
+test_expect_success 'skip an empty patch in the middle of an am session' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --skip &&
+	test_path_is_missing .git/rebase-apply &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	grep "Patch is empty." err &&
+	grep "If you want to record it as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --allow-empty &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 7f2956d77ad..9309becfe03 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -658,6 +658,7 @@ test_expect_success 'status in an am session: empty patch' '
 On branch am_empty
 You are in the middle of an am session.
 The current patch is empty.
+  (use "git am --allow-empty" to record this patch as an empty commit)
   (use "git am --skip" to skip this patch)
   (use "git am --abort" to restore the original branch)
 
diff --git a/wt-status.c b/wt-status.c
index 5d215f4e4f1..d578a0e9192 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1227,6 +1227,9 @@ static void show_am_in_progress(struct wt_status *s,
 		if (!s->state.am_empty_patch)
 			status_printf_ln(s, color,
 				_("  (fix conflicts and then run \"git am --continue\")"));
+		else
+			status_printf_ln(s, color,
+				_("  (use \"git am --allow-empty\" to record this patch as an empty commit)"));
 		status_printf_ln(s, color,
 			_("  (use \"git am --skip\" to skip this patch)"));
 		status_printf_ln(s, color,
-- 
gitgitgadget

Re: [PATCH v14 2/3] am: support --empty=<option> to handle empty patches

From: Johannes Schindelin <hidden>
Date: 2021-12-03 22:33:59

Hi Aleen,

On Wed, 1 Dec 2021, 徐沛文 (Aleen) via GitGitGadget wrote:
quoted hunk
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..cf9cace9678 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
I have to agree with Elijah that "to die" is used differently in Git's
context. This should probably be called "stop" instead. Or "error". But
not "die".

Ciao,
Dscho

P.S.: The enum value should probably have an `_ON_` in it, i.e.
`STOP_ON_EMPTY_COMMIT` or `ERROR_ON_EMPTY_COMMIT`.

[PATCH v15 0/3] am: support --empty=(die|drop|keep) option and --allow-empty option to handle empty patches

From: Aleen via GitGitGadget <hidden>
Date: 2021-12-06 09:41:46

Since that git has supported the --always option for the git-format-patch
command to create a patch with an empty commit message, git-am should
support applying and committing with empty patches.

----------------------------------------------------------------------------

Changes since v1:

 1. add a case when not passing the --always option.
 2. rename the --always option to --allow-empty.

----------------------------------------------------------------------------

Changes since v2:

 1. rename the --allow-empty option to --empty-commit.
 2. introduce three different strategies (die|skip|asis) when trying to
    record empty patches as empty commits.

----------------------------------------------------------------------------

Changes since v3:

 1. generate the missed file for test cases.
 2. grep -f cannot be used under Mac OS.

----------------------------------------------------------------------------

Changes since v4:

 1. rename the --empty-commit option to --empty.
 2. rename three different strategies (die|skip|asis) to die, drop and keep
    correspondingly.

----------------------------------------------------------------------------

Changes since v5:

 1. throw an error when passing --empty option without value.

----------------------------------------------------------------------------

Changes since v6:

 1. add i18n resources.

----------------------------------------------------------------------------

Changes since v7:

 1. update code according to the seen branch.
 2. fix the wrong document of git-am.
 3. sign off commits by a real name.

----------------------------------------------------------------------------

Changes since v8:

 1. update the committer's name with my real name to fix DCO of GGG.

----------------------------------------------------------------------------

Changes since v9:

 1. imitate the signed name format of
    https://lore.kernel.org/git/pull.1143.git.git.1637347813367.gitgitgadget@gmail.com
    .

----------------------------------------------------------------------------

Changes since v11:

 1. introduce an interactive option --allow-empty for git-am to record empty
    patches in the middle of an am session.

----------------------------------------------------------------------------

Changes since v12:

 1. record the empty patch as an empty commit only when there are no
    changes.
 2. fix indentation problems.
 3. simplify "to keep recording" to "to record".
 4. add a test case for skipping empty patches via the --skip option.

----------------------------------------------------------------------------

Changes since v13:

 1. add an additional description about the 'die' value.

----------------------------------------------------------------------------

Changes since v14:

 1. reimplement the 'die' value and stop the whole session. (Expected a
    reroll)
 2. the --allow-empty option is a valid resume value only when: (Expected a
    reroll)
    * index has not changed
    * lacking a patch

徐沛文 (Aleen) (3):
  doc: git-format-patch: describe the option --always
  am: support --empty=<option> to handle empty patches
  am: support --allow-empty to record specific empty patches

 Documentation/git-am.txt           |  16 ++++-
 Documentation/git-format-patch.txt |   6 +-
 builtin/am.c                       | 100 ++++++++++++++++++++++++----
 t/t4150-am.sh                      | 103 +++++++++++++++++++++++++++++
 t/t7512-status-help.sh             |   1 +
 wt-status.c                        |   3 +
 6 files changed, 214 insertions(+), 15 deletions(-)


base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v15
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v15
Pull-Request: https://github.com/gitgitgadget/git/pull/1076

Range-diff vs v14:

 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  b6a04fc12df ! 2:  8ec8e212672 am: support --empty=<option> to handle empty patches
     @@ Documentation/git-am.txt: OPTIONS
       	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
       
      +--empty=(die|drop|keep)::
     -+	By default, or when the option is set to 'die', the command
     -+	errors out on an input e-mail message lacking a patch
     -+	and stops into the middle of the current am session. When this
     -+	option is set to 'drop', skip such an e-mail message instead.
     ++	By default, the command errors out on an input e-mail message
     ++	lacking a patch and stops into the middle of the current am session.
     ++	When this option is set to 'die', the whole session dies with error.
     ++	When this option is set to 'drop', skip such an e-mail message instead.
      +	When this option is set to 'keep', create an empty commit,
      +	recording the contents of the e-mail message as its log.
      +
     @@ builtin/am.c: enum show_patch_type {
       };
       
      +enum empty_action {
     -+	DIE_EMPTY_COMMIT = 0,  /* output errors */
     ++	ERR_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */
     ++	DIE_EMPTY_COMMIT,      /* output errors and stop the whole am session */
      +	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
      +	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
      +};
     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
      +				to_keep = 1;
      +				break;
      +			case DIE_EMPTY_COMMIT:
     ++				am_destroy(state);
     ++				die(_("Patch is empty."));
     ++				break;
     ++			case ERR_EMPTY_COMMIT:
      +				printf_ln(_("Patch is empty."));
      +				die_user_resolve(state);
      +				break;
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
       		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
       		  N_("GPG-sign commits"),
       		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
     -+		OPT_CALLBACK_F(0, "empty", &state.empty_type, "{drop,keep,die}",
     ++		OPT_CALLBACK_F(ERR_EMPTY_COMMIT, "empty", &state.empty_type, "{die,drop,keep}",
      +		  N_("how to handle empty patches"),
      +		  PARSE_OPT_NONEG, am_option_parse_empty),
       		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +	test_cmp expected err
      +'
      +
     -+test_expect_success 'a message without a patch is an error (default)' '
     ++test_expect_success 'a message without a patch is an error and stop in the middle of an am session (default)' '
      +	test_when_finished "git am --abort || :" &&
      +	test_must_fail git am empty-commit.patch >err &&
     ++	test_path_is_dir .git/rebase-apply &&
      +	grep "Patch is empty" err
      +'
      +
     -+test_expect_success 'a message without a patch is an error where an explicit "--empty=die" is given' '
     -+	test_when_finished "git am --abort || :" &&
     -+	test_must_fail git am --empty=die empty-commit.patch >err &&
     -+	grep "Patch is empty." err
     ++test_expect_success 'a message without a patch is an error and exit where an explicit "--empty=die" is given' '
     ++	test_must_fail git am --empty=die empty-commit.patch 2>err &&
     ++	test_path_is_missing .git/rebase-apply &&
     ++	grep "fatal: Patch is empty." err
      +'
      +
      +test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
 3:  cbd822d4340 ! 3:  d669406a312 am: support --allow-empty to record specific empty patches
     @@ Commit message
          am: support --allow-empty to record specific empty patches
      
          This option helps to record specific empty patches in the middle
     -    of an am session.
     +    of an am session. However, it is a valid resume value only when:
     +
     +        1. index has not changed
     +        2. lacking a branch
      
          Signed-off-by: 徐沛文 (Aleen) [off-list ref]
      
     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.
      
       ## builtin/am.c ##
      @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
     - 				to_keep = 1;
     + 				die(_("Patch is empty."));
       				break;
     - 			case DIE_EMPTY_COMMIT:
     + 			case ERR_EMPTY_COMMIT:
      -				printf_ln(_("Patch is empty."));
      +				printf_ln(_("Patch is empty.\n"
      +					    "If you want to record it as an empty commit, run \"git am --allow-empty\"."));
     @@ builtin/am.c: next:
      -static void am_resolve(struct am_state *state)
      +static void am_resolve(struct am_state *state, int allow_empty)
       {
     ++	int index_changed;
     ++
       	validate_resume_state(state);
       
       	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
       
     - 	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
     +-	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
      -		printf_ln(_("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."));
     --		die_user_resolve(state);
     ++	/**
     ++	 * "--allow-empty" is a valid resume value only when:
     ++	 *   1. index has not changed
     ++	 *   2. lacking a patch
     ++	 */
     ++	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
     ++	if (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, "patch")))) {
     ++		printf_ln(_("Invalid resume value."));
     + 		die_user_resolve(state);
     + 	}
     + 
     ++	if (!index_changed) {
      +		if (allow_empty)
      +			printf_ln(_("No changes - record it as an empty commit."));
      +		else {
     @@ builtin/am.c: next:
      +				    "already introduced the same changes; you might want to skip this patch."));
      +			die_user_resolve(state);
      +		}
     - 	}
     - 
     ++	}
     ++
       	if (unmerged_cache()) {
     + 		printf_ln(_("You still have unmerged paths in your index.\n"
     + 			"You should 'git add' each file with resolved conflicts to mark them as such.\n"
      @@ builtin/am.c: enum resume_type {
       	RESUME_SKIP,
       	RESUME_ABORT,
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	git show HEAD --format="%s" >actual &&
      +	test_cmp actual expected
      +'
     ++
     ++test_expect_success 'cannot create empty commits when the index is changed' '
     ++	git checkout empty-commit^ &&
     ++	test_must_fail git am empty-commit.patch >err &&
     ++	: >empty-file &&
     ++	git add empty-file &&
     ++	test_must_fail git am --allow-empty >err &&
     ++	grep "Invalid resume value." err
     ++'
     ++
     ++test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '
     ++	test_when_finished "git am --abort || :" &&
     ++	git rev-parse HEAD >expected &&
     ++	test_must_fail git am seq.patch &&
     ++	test_must_fail git am --allow-empty >err &&
     ++	grep "Invalid resume value." err &&
     ++	git rev-parse HEAD >actual &&
     ++	test_cmp actual expected
     ++'
     ++
     ++test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '
     ++	test_when_finished "git am --abort || :" &&
     ++	git rev-parse HEAD >expected &&
     ++	test_must_fail git am -3 seq.patch &&
     ++	test_must_fail git am --allow-empty >err &&
     ++	grep "Invalid resume value." err &&
     ++	git rev-parse HEAD >actual &&
     ++	test_cmp actual expected
     ++'
      +
       test_done
      

-- 
gitgitgadget

[PATCH v15 1/3] doc: git-format-patch: describe the option --always

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-12-06 09:41:47

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

This commit has described how to use '--always' option in the command
'git-format-patch' to include patches for commits that emit no changes.

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 113eabc107c..be797d7a28f 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
-		   [--ignore-if-in-upstream]
+		   [--ignore-if-in-upstream] [--always]
 		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
@@ -192,6 +192,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--always::
+	Include patches for commits that do not introduce any change,
+	which are omitted by default.
+
 --cover-from-description=<mode>::
 	Controls which parts of the cover letter will be automatically
 	populated using the branch's description.
-- 
gitgitgadget

[PATCH v15 2/3] am: support --empty=<option> to handle empty patches

From: 徐沛文 (Aleen) via GitGitGadget <hidden>
Date: 2021-12-06 09:41:49

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 [off-list ref]

Since that the command 'git-format-patch' can include patches of
commits that emit no changes, the 'git-am' command should also
support an option, named as '--empty', to specify how to handle
those empty patches. In this commit, we have implemented three
valid options ('die', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <redacted>
---
 Documentation/git-am.txt |  9 ++++++
 builtin/am.c             | 60 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++
 3 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..4c31c39bf81 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -16,6 +16,7 @@ SYNOPSIS
 	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
 	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
 	 [--quoted-cr=<action>]
+	 [--empty=(die|drop|keep)]
 	 [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
 
@@ -63,6 +64,14 @@ OPTIONS
 --quoted-cr=<action>::
 	This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
 
+--empty=(die|drop|keep)::
+	By default, the command errors out on an input e-mail message
+	lacking a patch and stops into the middle of the current am session.
+	When this option is set to 'die', the whole session dies with error.
+	When this option is set to 'drop', skip such an e-mail message instead.
+	When this option is set to 'keep', create an empty commit,
+	recording the contents of the e-mail message as its log.
+
 -m::
 --message-id::
 	Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),
diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..43a3b1cf038 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,13 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	ERR_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */
+	DIE_EMPTY_COMMIT,      /* output errors and stop the whole am session */
+	DROP_EMPTY_COMMIT,     /* skip with a notice message, unless "--quiet" has been passed */
+	KEEP_EMPTY_COMMIT      /* keep recording as empty commits */
+};
+
 struct am_state {
 	/* state directory path */
 	char *dir;
@@ -118,6 +125,7 @@ struct am_state {
 	int message_id;
 	int scissors; /* enum scissors_type */
 	int quoted_cr; /* enum quoted_cr_action */
+	int empty_type; /* enum empty_action */
 	struct strvec git_apply_opts;
 	const char *resolvemsg;
 	int committer_date_is_author_date;
@@ -178,6 +186,25 @@ static int am_option_parse_quoted_cr(const struct option *opt,
 	return 0;
 }
 
+static int am_option_parse_empty(const struct option *opt,
+				     const char *arg, int unset)
+{
+	int *opt_value = opt->value;
+
+	BUG_ON_OPT_NEG(unset);
+
+	if (!strcmp(arg, "die"))
+		*opt_value = DIE_EMPTY_COMMIT;
+	else if (!strcmp(arg, "drop"))
+		*opt_value = DROP_EMPTY_COMMIT;
+	else if (!strcmp(arg, "keep"))
+		*opt_value = KEEP_EMPTY_COMMIT;
+	else
+		return error(_("Invalid value for --empty: %s"), arg);
+
+	return 0;
+}
+
 /**
  * Returns path relative to the am_state directory.
  */
@@ -1248,11 +1275,6 @@ static int parse_mail(struct am_state *state, const char *mail)
 		goto finish;
 	}
 
-	if (is_empty_or_missing_file(am_path(state, "patch"))) {
-		printf_ln(_("Patch is empty."));
-		die_user_resolve(state);
-	}
-
 	strbuf_addstr(&msg, "\n\n");
 	strbuf_addbuf(&msg, &mi.log_message);
 	strbuf_stripspace(&msg, 0);
@@ -1763,6 +1785,7 @@ static void am_run(struct am_state *state, int resume)
 	while (state->cur <= state->last) {
 		const char *mail = am_path(state, msgnum(state));
 		int apply_status;
+		int to_keep;
 
 		reset_ident_date();
 
@@ -1792,8 +1815,31 @@ static void am_run(struct am_state *state, int resume)
 		if (state->interactive && do_interactive(state))
 			goto next;
 
+		to_keep = 0;
+		if (is_empty_or_missing_file(am_path(state, "patch"))) {
+			switch (state->empty_type) {
+			case DROP_EMPTY_COMMIT:
+				say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg);
+				goto next;
+				break;
+			case KEEP_EMPTY_COMMIT:
+				to_keep = 1;
+				break;
+			case DIE_EMPTY_COMMIT:
+				am_destroy(state);
+				die(_("Patch is empty."));
+				break;
+			case ERR_EMPTY_COMMIT:
+				printf_ln(_("Patch is empty."));
+				die_user_resolve(state);
+				break;
+			}
+		}
+
 		if (run_applypatch_msg_hook(state))
 			exit(1);
+		if (to_keep)
+			goto commit;
 
 		say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
@@ -1827,6 +1873,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2404,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
 		  N_("GPG-sign commits"),
 		  PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+		OPT_CALLBACK_F(ERR_EMPTY_COMMIT, "empty", &state.empty_type, "{die,drop,keep}",
+		  N_("how to handle empty patches"),
+		  PARSE_OPT_NONEG, am_option_parse_empty),
 		OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
 			N_("(internal use for git-rebase)")),
 		OPT_END()
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 2aaaa0d7ded..f2b765644e5 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -196,6 +196,12 @@ test_expect_success setup '
 
 	git format-patch -M --stdout lorem^ >rename-add.patch &&
 
+	git checkout -b empty-commit &&
+	git commit -m "empty commit" --allow-empty &&
+
+	: >empty.patch &&
+	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
+
 	# reset time
 	sane_unset test_tick &&
 	test_tick
@@ -1152,4 +1158,48 @@ test_expect_success 'apply binary blob in partial clone' '
 	git -C client am ../patch
 '
 
+test_expect_success 'an empty input file is error regardless of --empty option' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=drop empty.patch 2>actual &&
+	echo "Patch format detection failed." >expected &&
+	test_cmp expected actual
+'
+
+test_expect_success 'invalid when passing the --empty option alone' '
+	test_when_finished "git am --abort || :" &&
+	git checkout empty-commit^ &&
+	test_must_fail git am --empty empty-commit.patch 2>err &&
+	echo "error: Invalid value for --empty: empty-commit.patch" >expected &&
+	test_cmp expected err
+'
+
+test_expect_success 'a message without a patch is an error and stop in the middle of an am session (default)' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am empty-commit.patch >err &&
+	test_path_is_dir .git/rebase-apply &&
+	grep "Patch is empty" err
+'
+
+test_expect_success 'a message without a patch is an error and exit where an explicit "--empty=die" is given' '
+	test_must_fail git am --empty=die empty-commit.patch 2>err &&
+	test_path_is_missing .git/rebase-apply &&
+	grep "fatal: Patch is empty." err
+'
+
+test_expect_success 'a message without a patch will be skipped when "--empty=drop" is given' '
+	git am --empty=drop empty-commit.patch >output &&
+	git rev-parse empty-commit^ >expected &&
+	git rev-parse HEAD >actual &&
+	test_cmp expected actual &&
+	grep "Skipping: empty commit" output
+'
+
+test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
+	git am --empty=keep empty-commit.patch &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%s" >expected &&
+	git show HEAD --format="%s" >actual &&
+	test_cmp actual expected
+'
+
 test_done
-- 
gitgitgadget
Next 22 of 22 remaining
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help