threads / patch / 56877

patch, 2 partsdoc: git-format-patch: specify the option --always

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

## tl;dr

129 messages between Nov 12, 2021 and Dec 11, 2021. Diffs are folded; open one to read it.

replies: 128people: 11as markdown or json

Aleen via GitGitGadget· Nov 12, 2021, 04:58 UTC · lore

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

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
Aleen via GitGitGadget· Nov 12, 2021, 04:58 UTC · re: Aleen via GitGitGadget · lore
From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 5 +++++
 1 file changed, 5 insertions(+)
Show changes to Documentation/git-format-patch.txt +5 −0
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
Aleen via GitGitGadget· Nov 12, 2021, 04:58 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  5 +++++
 builtin/am.c             | 18 ++++++++++++++++--
 t/t4150-am.sh            | 25 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)
Show changes to 3 files +46 −2

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
René Scharfe· Nov 12, 2021, 06:17 UTC · re: Aleen via GitGitGadget · lore

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

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é
Junio C Hamano· Nov 12, 2021, 06:47 UTC · re: René Scharfe · lore

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

René Scharfe <l.s.r@web.de> writes:
Show 7 quoted lines
> 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.
What symmetry?
> 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.

That one I can believe, even though I do not necessarily think it is a good idea to add such an option.

Aleen 徐沛文· Nov 12, 2021, 07:10 UTC · re: Junio C Hamano · lore

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

Dears Junio C Hamano:
Show 18 quoted lines
> René Scharfe <l.s.r@web.de> writes:
> 
> > 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.
> 
> What symmetry?
> 
> > 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.
> 
> That one I can believe, even though I do not necessarily think it is
> a good idea to add such an option.

In some cases when we need to migrate commits from one branch to another branch, from one repo to another repo, it should be necessary.

Aleen
René Scharfe· Nov 12, 2021, 15:28 UTC · re: Junio C Hamano · lore

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

Am 12.11.21 um 07:47 schrieb Junio C Hamano:
Show 11 quoted lines
> René Scharfe <l.s.r@web.de> writes:
>
>> 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.
>
> What symmetry?
To have the same option in both producer and consumer.
René
Junio C Hamano· Nov 12, 2021, 16:08 UTC · re: René Scharfe · lore

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

René Scharfe <l.s.r@web.de> writes:
Show 6 quoted lines
>>> The symmetry is compelling, but "always" is quite generic.  I can see
>>> e.g. someone expecting "git am --always" to imply --keep-non-patch.
>>
>> What symmetry?
>
> To have the same option in both producer and consumer.

Well, "--always" that stands for "--always-show-header" seeps through to the command line parser from revision.c layer but that is by accident, and is not even documented. We may want to file it as a bug and fix it later.

As you mentioned, allow-empty-commit does sound like a much better name.

Aleen· Nov 12, 2021, 06:42 UTC · re: René Scharfe · lore

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

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.

Aleen via GitGitGadget· Nov 12, 2021, 06:53 UTC · re: Aleen via GitGitGadget · lore

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

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
Aleen via GitGitGadget· Nov 12, 2021, 06:53 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 5 +++++
 1 file changed, 5 insertions(+)
Show changes to Documentation/git-format-patch.txt +5 −0
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
Junio C Hamano· Nov 12, 2021, 22:17 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Aleen <aleen42@vip.qq.com>
> Subject: Re: [PATCH v2 1/4] doc: git-format-patch: specify the option --always
"specify" -> "describe", perhaps?
Show 24 quoted lines
> Signed-off-by: Aleen <aleen42@vip.qq.com>
> ---
>  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.

Aleen via GitGitGadget· Nov 12, 2021, 06:53 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  5 +++++
 builtin/am.c             | 18 ++++++++++++++++--
 t/t4150-am.sh            | 25 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 2 deletions(-)
Show changes to 3 files +46 −2

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Junio C Hamano· Nov 12, 2021, 22:23 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Aleen <aleen42@vip.qq.com>
> 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.

Show 9 quoted lines
> 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;
>  };
> @@ -1249,8 +1251,12 @@ static int parse_mail(struct am_state *state, const char *mail)
>  	}
Show 10 quoted lines
>  	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.

Show 7 quoted lines
> @@ -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.

Show 23 quoted lines
> 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.

Show 5 quoted lines
> +	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.
Aleen via GitGitGadget· Nov 12, 2021, 06:53 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 t/t4150-am.sh | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)
Show changes to t/t4150-am.sh +11 −1
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
Aleen via GitGitGadget· Nov 12, 2021, 06:54 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  4 ++--
 builtin/am.c             |  8 ++++----
 t/t4150-am.sh            | 10 +++++-----
 3 files changed, 11 insertions(+), 11 deletions(-)
Show changes to 3 files +11 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Aleen via GitGitGadget· Nov 15, 2021, 10:39 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
      
       ## Commit message ##
     -    doc: git-format-patch: specify the option --always
     +    doc: git-format-patch: describe the option --always
      
          Signed-off-by: Aleen <aleen42@vip.qq.com>
      
       ## 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 <aleen42@vip.qq.com>
      
       ## Commit message ##
     -    am: support --always option to am empty commits
     +    am: support --empty-commit option to handle empty patches
      
          Signed-off-by: Aleen <aleen42@vip.qq.com>
      
     @@ 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
Aleen via GitGitGadget· Nov 15, 2021, 10:39 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 15, 2021, 10:39 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 48 +++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 58 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 110 insertions(+), 5 deletions(-)
Show changes to 3 files +110 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Aleen 徐沛文· Nov 15, 2021, 11:13 UTC · re: Aleen via GitGitGadget · lore

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

Dears Hamano,
Sorry about the late submission, and I have modified it to support the `--empty-commit=(die|skip|asis)` pattern.
Show 13 quoted lines
> +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
Aleen via GitGitGadget· Nov 16, 2021, 05:18 UTC · re: Aleen via GitGitGadget · lore

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

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
Aleen via GitGitGadget· Nov 16, 2021, 05:18 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 16, 2021, 05:18 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
Show changes to 3 files +125 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Phillip Wood· Nov 16, 2021, 10:07 UTC · re: Aleen via GitGitGadget · lore

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

Hi Aleen
Thanks for working on this
On 16/11/2021 05:18, Aleen via GitGitGadget wrote:
Show 32 quoted lines
> From: Aleen <aleen42@vip.qq.com>
> 
> Signed-off-by: Aleen <aleen42@vip.qq.com>
> ---
>   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
Show 194 quoted lines
>   -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
> 
Aleen 徐沛文· Nov 16, 2021, 10:31 UTC · re: Phillip Wood · lore

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

Show 18 quoted lines
> > +--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
Junio C Hamano· Nov 17, 2021, 08:39 UTC · re: Phillip Wood · lore

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

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 15 quoted lines
>>   +--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".

Aleen via GitGitGadget· Nov 17, 2021, 09:33 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
      
       ## Commit message ##
     -    am: support --empty-commit option to handle empty patches
     +    am: support --empty option to handle empty patches
      
          Signed-off-by: Aleen <aleen42@vip.qq.com>
      
     @@ 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
Aleen via GitGitGadget· Nov 17, 2021, 09:33 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 17, 2021, 09:33 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
Show changes to 3 files +125 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Aleen via GitGitGadget· Nov 18, 2021, 10:50 UTC · re: Aleen via GitGitGadget · lore

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

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
Aleen via GitGitGadget· Nov 18, 2021, 10:50 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 18, 2021, 10:50 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++
 builtin/am.c             | 48 +++++++++++++++++++++++---
 t/t4150-am.sh            | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 5 deletions(-)
Show changes to 3 files +125 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Junio C Hamano· Nov 19, 2021, 00:56 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Aleen <aleen42@vip.qq.com>

Here is a place to explain to readers what it exactly means to "support --empty option to handle empty patches". You know what you meant, I happen to know what you meant because I haven't forgotten yet, but we are writing for developers in the future who need to fix bugs in 6 months down the road to decide if this commit is involved in the bug in "git am" they are hunting.

Show 27 quoted lines
> Signed-off-by: Aleen <aleen42@vip.qq.com>
> ---
>  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.
"without outputting error" -> "instead".

The first sentence used "to error" as a verb to mean "stop the operation in the middle with a message that diagnoses what is wrong". Saying "outputting error" to use the same word as a noun is not a good idea, especially the use of verb "to output", as it makes the word "error" look as if it is only the "message" part, and it becomes unclear if the "stopping due to an error" is also skipped.

I actually think it makes sense to give a notice/info message when "drop" skips an empty patch, unless "--quiet" is given.

> +	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.

"is specified by default" sounds a bit strange. Perhaps lose the last sentence, and update the first line like so:

    By default, or when the option is set to 'die', the command
    errors out on an input e-mail message that lacks a patch.
Show 11 quoted lines
> 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 */
I think the user deserves "info:" (unless "--quiet").
Show 34 quoted lines
> +	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;
> +}
OK.
Show 12 quoted lines
> @@ -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;
It probably is easier to read without "else", i.e.
			else if (state->empty_type == KEEP_EMPTY_COMMIT) {
				if (run_applypatch_msg_hook(state))
					exit(1);
				goto commit;

Running hook here and outside this if() block a bit ugly. Can't we instead set a flag variable or something and let the existing call to run_applypatch_msg_hook() used?

> +			} else if (state->empty_type == DIE_EMPTY_COMMIT) {
> +				printf_ln(_("Patch is empty."));
> +				die_user_resolve(state);
> +			}
Also, if developers ever add another possible value for the
state->empty_type member (by the way, "empty" in its name is good,
but "type" is iffy), it would be a bug if they do not consider what
should happen inside this "if (is_empty_or_missing_file())" block,
no?  In such a case, instead of if/else if/... cascade, consider
using switch().  Taking them together.
 
	while (state->cur <= state->last) {
        	const char *mail = am_path(state, msgnum(state));
		int apply_status;
		int to_keep;
		...
		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 empty patch '%.*s",
					linelen(state->msg0, state->msg);
				goto next;
				break;
			case KEEP_EMPTY_COMMIT:
				to_keep = 1;
				break;
			case DIE_EMPTY_COMMIT:
				pritnf_ln(_("Patch is empty."));
				die_user_resolve(state);
				break;
			}
		}
		if (run_applypatch_msg_hook(state))
			exit(1);
		if (to_keep)
			goto commit; /* empty: just record a message */
or something like that.
Show 12 quoted lines
> 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 &&

Unless this is a test for "format-patch" command to make sure that the above command will not output a single byte, I would rather see the above line written like this:

	: >empty.patch &&
> +	git format-patch --stdout --cover-letter empty-commit^ >cover-letter.patch &&

This only creates a cover letter without anything, which sort of looks like an empty commit.

> +	git format-patch --always --stdout empty-commit^ >empty-commit.patch &&
And this gives another empty patch with only a message.
I would sort-of understand if there is
	git format-patch --stdout --always --cover-letter HEAD^ >both.patch

that has two e-mail messages in it, but what's the significance of testing both cover-letter.patch and empty-commit.patch?

The receiving end would not care what's in the actual e-mail message, as long as it lacks the patch part, no? Or am I missing some subtle reason why we want to test both?

Show 6 quoted lines
> @@ -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 &&

Nobody will understand "still", unless they know it used to be unconditionally a failure to feed an e-mail message that lacks a patch.

test_expect_success 'An empty input file is error regardless of --empty option' '
or something like that?
Show 10 quoted lines
> +	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 &&
If any command before "rm" fails, it won't be cleaned up.
    test_expect_success 'a message without a patch is an error (default)' '
	test_when_finished "git am --abort || :" &&
	git checkout --detach empty-commit^ &&
	test_must_fail git am empty-commit.patch >err &&
	grep "Patch is empty" err
    '

or something like that. The title says it tests the default case, so do not test --empty=die case in the same test. Use a separate test to do the same for the case where an explicit "--empty=die" is given.

As I said, I do not quite see the point of testing with both empty-commit.patch and cover-letter.patch (unless the latter has an empty commit and a cover letter, totally in two messages).

Show 13 quoted lines
> +	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 &&

The output 'err' is used by nobody; do not redirect it and just leave the standard output alone.

> +	test_path_is_missing .git/rebase-apply &&

Don't test this condition when testing success or failure from "am". That is too much implementation detail.

> +	git rev-parse empty-commit^ >expected &&
> +	git rev-parse HEAD >actual &&
> +	test_cmp expected actual &&
OK, we make sure that HEAD does not move.
> +	git am --empty=drop cover-letter.patch >err &&
> +	test_path_is_missing .git/rebase-apply &&
> +	test_cmp_rev empty-commit^ HEAD

Lose this one? As I said, I may be missing some subtle reason why both need to be tested---and if it is the case, test it in a separate test, not 2 of them in a single test like this.

Show 8 quoted lines
> +'
> +
> +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 "--" &&

Did this test pass? Without a trailing whitespace after double-dash, I do not think this shell snippet reproduces a patch e-mail correctly

In any case, don't do this. Imagine a future developer may want to improve the patch output by adding other things after the "-- " signature line, in addition to the "git version". Or perhaps the "git version" trailer gets removed by default for privacy concerns.

Should such changes be prevented from being made, only because this test expects exact output we have today? No.

Verify what we _care_, e.g. "does the title of the resulting commit reproduce the one-liner message given to '-m' when the empty commit was made?" is a sensible question to ask.

> +		git version | sed -e "s/^git version //" &&
> +		echo
Show 17 quoted lines
> +	} >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

Don't do this. Imagine a future developer may want to improve the cover letter template. Should that change be prevented from being made, because this test expects exact output we have today? No.

Show 6 quoted lines
> +	} >expected &&
> +	git show HEAD --format="%B" >actual &&
> +	test_cmp actual expected
> +'
> +
>  test_done
Thanks.
Bagas Sanjaya· Nov 19, 2021, 10:33 UTC · re: Aleen via GitGitGadget · lore

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

On 18/11/21 17.50, Aleen via GitGitGadget wrote:
Show 5 quoted lines
> +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
Ævar Arnfjörð Bjarmason· Nov 19, 2021, 12:10 UTC · re: Bagas Sanjaya · lore

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

On Fri, Nov 19 2021, Bagas Sanjaya wrote:
Show 8 quoted lines
> 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?

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.

Eric Sunshine· Nov 19, 2021, 12:20 UTC · re: Ævar Arnfjörð Bjarmason · lore

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

On Fri, Nov 19, 2021 at 7:12 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:

Show 16 quoted lines
> On Fri, Nov 19 2021, Bagas Sanjaya wrote:
> > 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?
>
> 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/
Junio C Hamano· Nov 19, 2021, 16:49 UTC · re: Eric Sunshine · lore

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

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 11 quoted lines
> 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.
It indeed is minor enough that a patch to turn an existing
	echo A B C >expect &&
	test_cmp expect actual
into
	echo "A B C" >expect &&
	test_cmp expect actual

is not welcome. But it still is worth pointing out and correcting in a patch to add new code, I would think. It all depends on what we care about, and the use of test_cmp means we do care about exact shape of the string, including the inter-word spacing.

Thanks.
Junio C Hamano· Nov 19, 2021, 16:46 UTC · re: Bagas Sanjaya · lore

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

Bagas Sanjaya <bagasdotme@gmail.com> writes:
Show 8 quoted lines
> 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?

You may doing so without realizing, but such a "why does the code not do X" question, especially without stating why you think the code should do X, gets irritating quickly.

    I think it is better to quote the arguments to echo to make it a
    single string, because ...
would have helped the author of the patch.

If this were one-off, I would have let it go, but I thought it may be worth commenting, as you seem to often ask this kind of "questions".

Aleen via GitGitGadget· Nov 18, 2021, 10:50 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>
Signed-off-by: Aleen <aleen42@vip.qq.com>
---
 builtin/am.c  | 13 +++++++------
 t/t4150-am.sh |  8 +++++++-
 2 files changed, 14 insertions(+), 7 deletions(-)
Show changes to 2 files +14 −7

builtin/am.c, t/t4150-am.sh

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
Junio C Hamano· Nov 19, 2021, 01:13 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Aleen <aleen42@vip.qq.com>

Here is a place for you to explain why this change might be a good idea to defend it. If we applied 1/3 and 2/3 but not this one, what happens? "am --empty" (not "am --empty=<choice>") is accepted and behaves just like "am --empty=die"? Is that a bad thing?

If it is unconditonally a bad thing, then this patch does not stand on its own. It should be squashed into the previous step; otherwise it would be "oops, we didn't think it through and failed to detect an argument-less --error as an error in the previous step, and here is a fix for that mistake". We try not to deliberately commit a mistake and then fix it to gain commit counts in this project (or putting it differently, we try to pretend we are better than we really are and did not make a mistake in the first place).

Or are you giving list participants and reviewers a choice between "'--empty' is a synonym to '--empty=die'" and "'--empty' alone is an error" because you cannot decide yourself? If that is the case, you really really need to make it clear in this place between "From:" and "Signed-off-by:". Why would we want to use this patch? Why would we be better without this patch?

I haven't thought things through, but my gut feeling is that the semantics of the --empty=<choice> option is better _with_ this, in other words, it makes more sense to forbid "--empty" alone. So if I were doing this series, I would probably have squashed this into the previous one, making it a two-patch series.

Thanks.
Show 5 quoted lines
> Signed-off-by: Aleen <aleen42@vip.qq.com>
> ---
>  builtin/am.c  | 13 +++++++------
>  t/t4150-am.sh |  8 +++++++-
>  2 files changed, 14 insertions(+), 7 deletions(-)
Show 16 quoted lines
> 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);
Show 38 quoted lines
> +	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 &&
Aleen 徐沛文· Nov 19, 2021, 02:11 UTC · re: Junio C Hamano · lore

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

Show 28 quoted lines
> Here is a place for you to explain why this change might be a good
> idea to defend it.  If we applied 1/3 and 2/3 but not this one, what
> happens?  "am --empty" (not "am --empty=<choice>") is accepted and
> behaves just like "am --empty=die"?  Is that a bad thing?
> 
> If it is unconditonally a bad thing, then this patch does not stand
> on its own.  It should be squashed into the previous step; otherwise
> it would be "oops, we didn't think it through and failed to detect
> an argument-less --error as an error in the previous step, and here
> is a fix for that mistake".  We try not to deliberately commit a
> mistake and then fix it to gain commit counts in this project (or
> putting it differently, we try to pretend we are better than we
> really are and did not make a mistake in the first place).
> 
> Or are you giving list participants and reviewers a choice between
> "'--empty' is a synonym to '--empty=die'" and "'--empty' alone is an
> error" because you cannot decide yourself?  If that is the case, you
> really really need to make it clear in this place between "From:"
> and "Signed-off-by:".  Why would we want to use this patch?  Why
> would we be better without this patch?
> 
> I haven't thought things through, but my gut feeling is that the
> semantics of the --empty=<choice> option is better _with_ this,
> in other words, it makes more sense to forbid "--empty" alone.  So
> if I were doing this series, I would probably have squashed this
> into the previous one, making it a two-patch series.
> 
> Thanks.
Hamano, I could not make this decision like what you said between "'--empty' is a synonym to '--empty=die'" and "'--empty' alone is an error". In my opinion, it is a little wired that '--empty' should not act as "died" semantically. Reversely, it may be better to support a synonym between '--quiet' and '--empty=skip'.
I will squash the commit into the previous one later and hope all participants and reviewers make a decision.
Junio C Hamano· Nov 18, 2021, 23:47 UTC · re: Aleen via GitGitGadget · lore

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

As per https://git-scm.com/docs/SubmittingPatches#sign-off:
   Please don’t hide your real name.

I suspect your real name is not Aleen, but something with x and w in it.

Aleen 徐沛文· Nov 19, 2021, 01:45 UTC · re: Junio C Hamano · lore

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

Show 10 quoted lines
> -----Original Messages-----From:"Junio C Hamano" <gitster@pobox.com>Sent Time:2021-11-19 07:47:11 (Friday)To:"Aleen via GitGitGadget" <gitgitgadget@gmail.com>Cc:git@vger.kernel.org, "René Scharfe" <l.s.r@web.de>, "Phillip Wood" <phillip.wood123@gmail.com>, "Aleen 徐沛文" <pwxu@coremail.cn>, Aleen <aleen42@vip.qq.com>Subject:Re: [PATCH v6 0/3] am: support --empty=(die|drop|keep) option to handle empty patches
> 
> 
> As per https://git-scm.com/docs/SubmittingPatches#sign-off:
> 
>    Please don’t hide your real name.
> 
> I suspect your real name is not Aleen, but something with x and w in
> it.
>
I have used "Aleen" <aleen42@vip.qq.com> as my GitHub account to send this pull request, but the e-mail service disgusts me that it is slow to send emails to the domain "vger.kernel.org". So I decided to use another mail service to discuss, "Aleen 徐沛文" <pwxu@coremail.cn> as you saw.
In the open-source world, I usually signed off "Aleen" rather than "Aleen 徐沛文", and is that necessary to change?
Junio C Hamano· Nov 19, 2021, 05:46 UTC · re: Aleen 徐沛文 · lore

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

Aleen 徐沛文 <pwxu@coremail.cn> writes:
Show 16 quoted lines
>> As per https://git-scm.com/docs/SubmittingPatches#sign-off:
>> 
>>    Please don’t hide your real name.
>> 
>> I suspect your real name is not Aleen, but something with x and w in
>> it.
>>
>
> I have used "Aleen" <aleen42@vip.qq.com> as my GitHub account to
> send this pull request, but the e-mail service disgusts me that it
> is slow to send emails to the domain "vger.kernel.org". So I
> decided to use another mail service to discuss, "Aleen 徐沛文"
> <pwxu@coremail.cn> as you saw.
>
> In the open-source world, I usually signed off "Aleen" rather than
> "Aleen 徐沛文", and is that necessary to change?
Yes.

As the URL you were referred to explains, the sign-off procedure is to keep track of provenance of the code, which is a more "legal" formal requirement than just "I use this pseudonym everywhere". When a big company comes to us, claiming that "this code is our intellectual property stolen from us" and pointing at code added by a patch from you, we do not want to see us in the position to have to say "eh, somebody who uses psuedonym X signed DCO, but we do not even know their real name".

Aleen 徐沛文· Nov 19, 2021, 07:23 UTC · re: Junio C Hamano · lore

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

Show 10 quoted lines
> Yes.
> 
> As the URL you were referred to explains, the sign-off procedure is
> to keep track of provenance of the code, which is a more "legal"
> formal requirement than just "I use this pseudonym everywhere".
> When a big company comes to us, claiming that "this code is our
> intellectual property stolen from us" and pointing at code added by
> a patch from you, we do not want to see us in the position to have
> to say "eh, somebody who uses psuedonym X signed DCO, but we do not
> even know their real name".

I know it, and as I said before that gitgitgadget need PR creators to sign off user name of GitHub account, according to the DCO check. I can confirmed that "Aleen" and "Aleen 徐沛文" are both the real name of mine, the committer. I can use the account aleen42@vip.qq.com to confirm this.

Aleen· Nov 19, 2021, 07:25 UTC · re: Aleen 徐沛文 · lore

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

> I know it, and as I said before that gitgitgadget need PR creators to
> sign off user name of GitHub account, according to the DCO check. I can
> confirmed that "Aleen" and "Aleen 徐沛文" are both the real name of mine,
> the committer. I can use the account aleen42@vip.qq.com to confirm this.
Confirmed.
Junio C Hamano· Nov 19, 2021, 16:54 UTC · re: Aleen 徐沛文 · lore

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

Aleen 徐沛文 <pwxu@coremail.cn> writes:
Show 15 quoted lines
>> Yes.
>> 
>> As the URL you were referred to explains, the sign-off procedure is
>> to keep track of provenance of the code, which is a more "legal"
>> formal requirement than just "I use this pseudonym everywhere".
>> When a big company comes to us, claiming that "this code is our
>> intellectual property stolen from us" and pointing at code added by
>> a patch from you, we do not want to see us in the position to have
>> to say "eh, somebody who uses psuedonym X signed DCO, but we do not
>> even know their real name".
>
> I know it, and as I said before that gitgitgadget need PR creators to
> sign off user name of GitHub account, according to the DCO check. I can
> confirmed that "Aleen" and "Aleen 徐沛文" are both the real name of mine,
> the committer. I can use the account aleen42@vip.qq.com to confirm this.

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.

Aleen 徐沛文· Nov 19, 2021, 17:14 UTC · re: Junio C Hamano · lore

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

Show 6 quoted lines
> 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.

When it comes to GGG, I just want to know whether this is the only way to contribute to Git? I think we can directly use GitHub to run the reviewing procedure, rather than sending emails in this old fashioned way, since that Git code has been maintained in GitHub. E-mails does well in playing the role as a notification way. They helps nothing in reviewing code, especially when we need to point out problems or suggestions referred to certain code snippets.

Junio C Hamano· Nov 19, 2021, 19:25 UTC · re: Aleen 徐沛文 · lore

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

Aleen 徐沛文 <pwxu@coremail.cn> writes:
Show 11 quoted lines
>> 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.
>
> When it comes to GGG, I just want to know whether this is the only
> way to contribute to Git? I think we can directly use GitHub to
> run the reviewing procedure, rather than sending emails in this old
> fashioned way, since that Git code has been maintained in GitHub.

No, we do not develop or maintain at GitHub at all. The repository at GitHub is one of the several publishing point and development is done here.

Johannes Schindelin· Nov 22, 2021, 11:57 UTC · re: Junio C Hamano · lore

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

Hi Junio & Aleen,
On Fri, 19 Nov 2021, Junio C Hamano wrote:
Show 24 quoted lines
> Aleen 徐沛文 <pwxu@coremail.cn> writes:
>
> >> Yes.
> >>
> >> As the URL you were referred to explains, the sign-off procedure is
> >> to keep track of provenance of the code, which is a more "legal"
> >> formal requirement than just "I use this pseudonym everywhere".
> >> When a big company comes to us, claiming that "this code is our
> >> intellectual property stolen from us" and pointing at code added by
> >> a patch from you, we do not want to see us in the position to have
> >> to say "eh, somebody who uses psuedonym X signed DCO, but we do not
> >> even know their real name".
> >
> > I know it, and as I said before that gitgitgadget need PR creators to
> > sign off user name of GitHub account, according to the DCO check. I can
> > confirmed that "Aleen" and "Aleen 徐沛文" are both the real name of mine,
> > the committer. I can use the account aleen42@vip.qq.com to confirm this.
>
> 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.

GitGitGadget looks at the author information, so you need to ensure that the "Author:" line in the output of `git log` shows the desired name. If it does not, you need to configure `user.name` (and user.email)` as desired, and then re-commit with `git commit --amend --reset-author`, then force-push.

Ciao, Johannes

Aleen 徐沛文· Nov 19, 2021, 04:16 UTC · re: Junio C Hamano · lore

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

Show 6 quoted lines
> As per https://git-scm.com/docs/SubmittingPatches#sign-off:
> 
>    Please don’t hide your real name.
> 
> I suspect your real name is not Aleen, but something with x and w in
> it.

BTW, gitgitgadget has defined that developers should sign off the user name of the GitHub account, and I can't change it at all.

Aleen via GitGitGadget· Nov 19, 2021, 05:04 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
      
       ## Documentation/git-format-patch.txt ##
 2:  96d8573dc80 ! 2:  9e60e77c041 am: support --empty option to handle empty patches
     @@ Metadata
      Author: Aleen <aleen42@vip.qq.com>
      
       ## 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 <aleen42@vip.qq.com>
      
     @@ 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 <fatzer2@gmail.com>, 2020
     + # Dimitriy Ryazantcev <DJm00n@mail.ru>, 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
Aleen via GitGitGadget· Nov 19, 2021, 05:04 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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 <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 19, 2021, 05:04 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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 <aleen42@vip.qq.com>
---
 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(-)
Show changes to 21 files +140 −28

Documentation/git-am.txt, builtin/am.c, po/bg.po, po/ca.po, po/de.po, po/el.po, po/es.po, po/fr.po, po/git.pot, po/id.po, po/it.po, po/ko.po, po/pl.po, po/pt_PT.po, po/ru.po, po/sv.po, po/tr.po, po/vi.po, po/zh_CN.po, po/zh_TW.po, t/t4150-am.sh

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
Junio C Hamano· Nov 19, 2021, 22:50 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 13 quoted lines
> From: Aleen <aleen42@vip.qq.com>
>
> 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 <aleen42@vip.qq.com>
> ---
>  Documentation/git-am.txt |  8 ++++++
>  builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
>  t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
Show 18 quoted lines
>  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 ++++-

Please do not touch these po/ files. They will be updated when i18n coordinator decides to update the translation.

Junio C Hamano· Nov 19, 2021, 23:07 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +test_expect_success 'An empty input file is error regardless of --empty option' '

Titles of all the other tests seem to begin with lowercase, so "An empty" -> "an empty", probably.

> +	test_must_fail git am --empty=drop empty.patch 2>actual &&
> +	echo Patch format detection failed. >expected &&
Quote for exactness, like the next test does.
    echo "Patch format detection failed." >expected
Show 9 quoted lines
> +	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
> +'

This mode of failure of "am" may not leave ".git/rebase-apply" behind right now, or it may leave one in the future. We do not want to worry about left-over cruft to interfere with the next test, so cleaning after ourselves with test_when_finished would be a good idea, i.e.

    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
    '
Show 6 quoted lines
> +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
> +'

And the point of test_when_finished is to run the clean-up even when other steps in the test fails. For example, "test_must_fail git am" may fail to fail for any reason. Or "grep" after it may fail. Because the pieces in a single test is strung together with &&, any such failure means the control would NOT reach "rm -fr".

Since we have test_when_finished that cleans up after ourselves even in such a case, the last "rm -fr" step is unnecessary.

Show 6 quoted lines
> +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
> +'
Likewise.

Other than that (and po/ that should not be part of this patch), things are looking good.

Thanks.
Aleen via GitGitGadget· Nov 22, 2021, 06:46 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
     +    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>
      
       ## 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 <aleen42@vip.qq.com>
     +    Signed-off-by: Aleen 徐沛文 <aleen42@vip.qq.com>
      
       ## 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 <fatzer2@gmail.com>, 2020
     - # Dimitriy Ryazantcev <DJm00n@mail.ru>, 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
Aleen via GitGitGadget· Nov 22, 2021, 06:46 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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 徐沛文 <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen via GitGitGadget· Nov 22, 2021, 06:46 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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 徐沛文 <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Junio C Hamano· Nov 22, 2021, 07:06 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 9 quoted lines
> From: Aleen <aleen42@vip.qq.com>
>
> 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 徐沛文 <aleen42@vip.qq.com>

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.
Aleen 徐沛文· Nov 22, 2021, 07:19 UTC · re: Junio C Hamano · lore

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

Show 16 quoted lines
> 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.
It seems there is something wrong when submitting via "徐沛文 (Aleen) <aleen42@vip.qq.com>".
Aleen via GitGitGadget· Nov 22, 2021, 07:02 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
     +Author: Aleen 徐沛文 <aleen42@vip.qq.com>
      
       ## 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 <aleen42@vip.qq.com>
     +Author: Aleen 徐沛文 <aleen42@vip.qq.com>
      
       ## Commit message ##
          am: support --empty=<option> to handle empty patches
-- 
gitgitgadget
Aleen 徐沛文 via GitGitGadget· Nov 22, 2021, 07:02 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <aleen42@vip.qq.com>

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 徐沛文 <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Aleen 徐沛文 via GitGitGadget· Nov 22, 2021, 07:02 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?Aleen=20=E5=BE=90=E6=B2=9B=E6=96=87?= <aleen42@vip.qq.com>

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 徐沛文 <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Aleen 徐沛文· Nov 22, 2021, 07:04 UTC · re: Aleen 徐沛文 via GitGitGadget · lore

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

Show 6 quoted lines
> 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.
Aleen via GitGitGadget· Nov 22, 2021, 07:51 UTC · re: Aleen via GitGitGadget · lore

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

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 徐沛文 <aleen42@vip.qq.com>
     +Author: Aleen <aleen42@vip.qq.com>
      
       ## 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 徐沛文 <aleen42@vip.qq.com>
     +    Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
       ## 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 徐沛文 <aleen42@vip.qq.com>
     +Author: Aleen <aleen42@vip.qq.com>
      
       ## 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 徐沛文 <aleen42@vip.qq.com>
     +    Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
       ## Documentation/git-am.txt ##
      @@ Documentation/git-am.txt: SYNOPSIS
-- 
gitgitgadget
Aleen via GitGitGadget· Nov 22, 2021, 07:51 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Johannes Schindelin· Nov 22, 2021, 12:00 UTC · re: Aleen via GitGitGadget · lore

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

Hi Aleen,
On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
> From: Aleen <aleen42@vip.qq.com>

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

Aleen 徐沛文· Nov 23, 2021, 01:25 UTC · re: Johannes Schindelin · lore

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

Show 17 quoted lines
> Hi Aleen,
> 
> On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
> 
> > From: Aleen <aleen42@vip.qq.com>
> 
> 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

Johannes Schindelin· Nov 23, 2021, 12:30 UTC · re: Aleen 徐沛文 · lore

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

Hi Aleen,
On Tue, 23 Nov 2021, Aleen 徐沛文 wrote:
Show 20 quoted lines
> > Hi Aleen,
> >
> > On Mon, 22 Nov 2021, Aleen via GitGitGadget wrote:
> >
> > > From: Aleen <aleen42@vip.qq.com>
> >
> > 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

Aleen via GitGitGadget· Nov 22, 2021, 07:51 UTC · re: Aleen via GitGitGadget · lore

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

From: Aleen <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Aleen via GitGitGadget· Nov 23, 2021, 15:26 UTC · re: Aleen via GitGitGadget · lore

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

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 <aleen42@vip.qq.com>
     +Author: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
       ## 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 <aleen42@vip.qq.com>
     +Author: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
       ## Commit message ##
          am: support --empty=<option> to handle empty patches
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Nov 23, 2021, 15:26 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Johannes Schindelin· Nov 23, 2021, 16:12 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

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?=
>  <aleen42@vip.qq.com>

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

Junio C Hamano· Nov 23, 2021, 22:02 UTC · re: Johannes Schindelin · lore

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

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 11 quoted lines
> 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?=
>>  <aleen42@vip.qq.com>
>
> 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.
;-)  Thanks for double-checking carefully.

These two are identical to what has been in my tree (as I manually tweaked the patches from the previous rounds).

1: 6112004764 = 1: e9181bd6fd doc: git-format-patch: describe the option --always 2: b26f121395 = 2: ca426b823b am: support --empty=<option> to handle empty patches

I'll pretend that I took v11 but please do not get alarmed if the author and committer times are from v10.

Thanks, both.
徐沛文 (Aleen) via GitGitGadget· Nov 23, 2021, 15:26 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Elijah Newren· Nov 26, 2021, 20:14 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

On Tue, Nov 23, 2021 at 8:38 AM 徐沛文 (Aleen) via GitGitGadget <gitgitgadget@gmail.com> wrote:

Show 39 quoted lines
>
> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
>  <aleen42@vip.qq.com>
>
> 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) <aleen42@vip.qq.com>
> ---
>  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
"""
Show 188 quoted lines
> +
>  -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
Aleen 徐沛文· Nov 29, 2021, 09:19 UTC · re: Elijah Newren · lore

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

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
Show 53 quoted lines
> > +--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
> """
Aleen 徐沛文· Nov 29, 2021, 10:00 UTC · re: Aleen 徐沛文 · lore

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

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?

Show 12 quoted lines
> 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
Elijah Newren· Nov 29, 2021, 17:10 UTC · re: Aleen 徐沛文 · lore

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

On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 <pwxu@coremail.cn> 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.

Show 12 quoted lines
> > 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
Aleen 徐沛文· Nov 30, 2021, 05:45 UTC · re: Elijah Newren · lore

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

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.

Show 34 quoted lines
> On Mon, Nov 29, 2021 at 2:00 AM Aleen 徐沛文 <pwxu@coremail.cn> 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.
> 
> > > 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
Junio C Hamano· Nov 29, 2021, 18:17 UTC · re: Elijah Newren · lore

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

Elijah Newren <newren@gmail.com> writes:
Show 10 quoted lines
>> +--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?

I think it is the same as how "git am" without this sees a piece of e-mail without any patch in it. It exits with non-zero status, but keeps the contents of .git/rebase-apply directory intact so that the user can decide what the next action should be. "the command stops with non-zero status and gives control back to the user" might be a better explanation.

As to the name of the option's value, I think 'die' is a much better word than 'ask' for this behaviour. It is not like we retain control, give "do you want to do X or Y?" prompt and do either X or Y ourselves, which is what I expect out of 'ask'. If we just exit with non-zero value and give the control back to the user, then we are not asking.

This is a tangent, but in the current Documentation/git-am.txt, the phrasing for "--skip" and "--resolved" are a bit inconsistent.

 - The "--skip" operation calls the situation it is designed to handle
   "an aborted patch".
 - The "--resolved" operation calls it "a patch failure".

The "--abort" operation interestingly does not use any word to explain the situation it is designed to be used in. It just assumes that the user knows "git am" session is somehow still in effect.

Elijah Newren· Nov 29, 2021, 18:57 UTC · re: Junio C Hamano · lore

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

On Mon, Nov 29, 2021 at 10:17 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 27 quoted lines
>
> Elijah Newren <newren@gmail.com> writes:
>
> >> +--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?
>
> I think it is the same as how "git am" without this sees a piece of
> e-mail without any patch in it.  It exits with non-zero status, but
> keeps the contents of .git/rebase-apply directory intact so that the
> user can decide what the next action should be.  "the command stops
> with non-zero status and gives control back to the user" might be a
> better explanation.
>
> As to the name of the option's value, I think 'die' is a much better
> word than 'ask' for this behaviour.  It is not like we retain
> control, give "do you want to do X or Y?"  prompt and do either X or
> Y ourselves, which is what I expect out of 'ask'.  If we just exit
> with non-zero value and give the control back to the user, then we
> are not asking.

Well, I still think 'die' implies aborting the entire overall `am` operation, and is thus a confusing term. To me, 'stop' or 'interrupt' would be better if you want a one-word term and don't like 'ask'.

If you don't like the term 'ask' here, perhaps we should also change rebase? rebase --empty has 'ask' as a choice for stopping/interrupting the rebase operation and letting the user decide how to handle it. (There is a very subtly different context for rebase's --empty=ask, namely in that it is only triggered for patches that were not originally empty but become so do the the new base the patches are being applied upon already having the changes from the patch in question, but that doesn't seem like a big enough difference to suggest it should have a different name.)

There's another bit to my query on the semantics, though. The second half of my critique was that the current form of the series does not inform the user how to handle the situation when `git-am` stops. It only tells the user how to drop the patch. (If there's only one option, why doesn't git just take it automatically?) I think it should also tell the user how to keep the patch. Now, if both options are presented to the user when the operation stops, would that qualify as the user having been 'asked' what to do?

Aleen via GitGitGadget· Nov 30, 2021, 05:37 UTC · re: Aleen via GitGitGadget · lore

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

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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 05:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 05:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 05:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 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(-)
Show changes to 5 files +40 −6

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

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
Junio C Hamano· Nov 30, 2021, 07:21 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

""徐沛文 (Aleen)" via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 50 quoted lines
> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
>  <aleen42@vip.qq.com>
>
> This option helps to record specific empty patches in the middle
> of an am session.
>
> Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
> ---
>  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\"."));

An overly deep indentation. Make "If" align with "Patch", probably. Also "to keep recording it" -> "to record it".

"If you want to record the e-mail text as the log message of an empty commit" may be a bit too much, but just saying "record it" may not be clear enough to explain what gets recorded how.

Show 14 quoted lines
> @@ -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;
> +	}
Lose {} around a single statement block.

But more importantly, doesn't this break "git am" when the user gives "--allow-empty" in cases where you are not expecting?

Like when the patch application (with or without -3) fails, either during "git am -i" or "git am" without "-i"?

The parameter claims to be "allow empty", implying that the option is about allowing to end up in an empty commit (and depending on the input, the resulting commit may not be empty and that is OK). But what the code does is to _ignore_ the unmerged check, not recording the manual resolution the user did for later reuse, and force a call to do_commit() fail if the index is still unmerged.

>  	validate_resume_state(state);
>  
>  	say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);

I suspect that the real code that wants to be changed is below this line, where we say "if the index does not have changes (relative to the HEAD), then complain and die". Perhaps it should become something along this line:

	if (!repo_index_has_changes(...)) {
		if (allow_empty) {
			printf_ln(_("No changes - recording an empty commit.");
		} else {
			printf_ln(_("No changes - did you forget ..."
				    "..."));
			die_user_resolve(state);
		}
	}

i.e. "when the index does not have changes, tell the user that we'd create an empty commit, if allow-empty is in effect. otherwise, complain and die".

I suspect that the worst failure mode may be "git am" (without -3) first fails to apply a patch (hence the index is unchanged wrt HEAD), then "git am" that is run with "--allow-empty" ignores the failure and creates an empty commit. Even with the "something along this line" change above, such a failure mode still exists. You really need to see if we had an input without any patch and do the skipping only when it is the case, I think.

Aleen via GitGitGadget· Nov 30, 2021, 09:55 UTC · re: Aleen via GitGitGadget · lore

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

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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 09:55 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 09:55 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  8 ++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 107 insertions(+), 5 deletions(-)
Show changes to 3 files +107 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
徐沛文 (Aleen) via GitGitGadget· Nov 30, 2021, 09:55 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 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(-)
Show changes to 5 files +53 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

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
Aleen via GitGitGadget· Dec 1, 2021, 03:37 UTC · re: Aleen via GitGitGadget · lore

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

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
徐沛文 (Aleen) via GitGitGadget· Dec 1, 2021, 03:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Dec 1, 2021, 03:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 5 deletions(-)
Show changes to 3 files +108 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
Johannes Schindelin· Dec 3, 2021, 22:30 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

Hi Aleen,
On Wed, 1 Dec 2021, 徐沛文 (Aleen) via GitGitGadget wrote:
Show 9 quoted lines
> 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`.

徐沛文 (Aleen) via GitGitGadget· Dec 1, 2021, 03:37 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 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(-)
Show changes to 5 files +54 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

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
Junio C Hamano· Dec 2, 2021, 00:58 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

""徐沛文 (Aleen)" via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 23 quoted lines
> +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
> +'

These two are "positive" tests, i.e. the feature does the right thing when used in the expected situation.

I am worried about the cases where "--allow-empty" is used and creates a commit that is empty, when it is reasonably expected to either create a non-empty commit or still fail the same way to give the user another chance to try.

For example, when "git am -3" fails on such an email message (there are two failure modes: (a) one results in an unmerged index due to merge conflicts, (b) the other results in a clean index due to not finding the blob object recorded as a preimage), what happens when the user runs "git am --allow-empty", after:

 (1) doing nothing since the failed "git am -3" application,
 (2) resolving the conflicts in working tree, or
 (3) resolving the conflicts in working tree and recording the
     resolution in the index.

There are 6 cases in the above matrix, and "git am --allow-empty" should not create an empty commit in any one of them. In 1a and 1b, "git am --allow-empty" should continue to fail ("git am --skip" would be a way to ignore and proceed). 3a and 3b should record a non-empty commit as the result.

Similarly, when an email message with a patch is given to "git am" and the application fails, and the user runs "git am --allow-empty" without doing anything in the working tree, what happens? It should not happily create an empty commit with only the log message, without the change, but continue to fail. If the user resolves and registers the resolution to the index before "git am --allow-empty", then the command should create a non-empty commit.

Having positive tests are of course important to avoid regression in the future, but negative tests, the new and shiny feature does not misbehave when it shouldn't even kick in, is even more important.

Thanks.
Aleen 徐沛文· Dec 6, 2021, 01:35 UTC · re: Junio C Hamano · lore

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

Dears Hamano,
    For those 6 cases, it seems `git-am` should only work when the am session
    was stopped in the middle according to an empty patch, but not when the index
    has not changed. Does that mean we should record a new state for the am state?
    If that's right, I will try to patch it via version 15, and try to add additional
    test cases for these 6 situations. I was a little busy those days and sorry for
    the late answer.
Aleen
Show 38 quoted lines
> These two are "positive" tests, i.e. the feature does the right
> thing when used in the expected situation.
> 
> I am worried about the cases where "--allow-empty" is used and
> creates a commit that is empty, when it is reasonably expected to
> either create a non-empty commit or still fail the same way to give
> the user another chance to try.
> 
> For example, when "git am -3" fails on such an email message (there
> are two failure modes: (a) one results in an unmerged index due to
> merge conflicts, (b) the other results in a clean index due to not
> finding the blob object recorded as a preimage), what happens when
> the user runs "git am --allow-empty", after:
> 
>  (1) doing nothing since the failed "git am -3" application,
>  (2) resolving the conflicts in working tree, or
>  (3) resolving the conflicts in working tree and recording the
>      resolution in the index.
> 
> There are 6 cases in the above matrix, and "git am --allow-empty"
> should not create an empty commit in any one of them.  In 1a and 1b,
> "git am --allow-empty" should continue to fail ("git am --skip"
> would be a way to ignore and proceed).  3a and 3b should record a
> non-empty commit as the result.
> 
> Similarly, when an email message with a patch is given to "git am"
> and the application fails, and the user runs "git am --allow-empty"
> without doing anything in the working tree, what happens?  It should
> not happily create an empty commit with only the log message,
> without the change, but continue to fail.  If the user resolves and
> registers the resolution to the index before "git am --allow-empty",
> then the command should create a non-empty commit.
> 
> Having positive tests are of course important to avoid regression in
> the future, but negative tests, the new and shiny feature does not
> misbehave when it shouldn't even kick in, is even more important.
> 
> Thanks.
Aleen via GitGitGadget· Dec 6, 2021, 09:41 UTC · re: Aleen via GitGitGadget · lore

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

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) <aleen42@vip.qq.com>
      
     @@ 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
徐沛文 (Aleen) via GitGitGadget· Dec 6, 2021, 09:41 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Dec 6, 2021, 09:41 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 ++++++
 builtin/am.c             | 60 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++
 3 files changed, 114 insertions(+), 5 deletions(-)
Show changes to 3 files +114 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

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
徐沛文 (Aleen) via GitGitGadget· Dec 6, 2021, 09:41 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

This option helps to record specific empty patches in the middle 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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  7 +++++-
 builtin/am.c             | 42 ++++++++++++++++++++++++-------
 t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 96 insertions(+), 10 deletions(-)
Show changes to 5 files +96 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 4c31c39bf81..a5223143222 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 43a3b1cf038..974924cb83d 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1830,7 +1830,8 @@ static void am_run(struct am_state *state, int resume)
 				die(_("Patch is empty."));
 				break;
 			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\"."));
 				die_user_resolve(state);
 				break;
 			}
@@ -1903,21 +1904,39 @@ 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)
 {
+	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)) {
-		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."));
+	/**
+	 * "--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 {
+			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()) {
 		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"
@@ -2242,7 +2261,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2395,6 +2415,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")),
@@ -2503,7 +2526,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 f2b765644e5..3f81aa3d6a1 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1202,4 +1202,57 @@ 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_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
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
Aleen via GitGitGadget· Dec 7, 2021, 05:01 UTC · re: Aleen via GitGitGadget · lore

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

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
----------------------------------------------------------------------------
Changes since v15:
 1. rename "die" to "stop".
徐沛文 (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                       |  90 +++++++++++++++++++++----
 t/t4150-am.sh                      | 102 +++++++++++++++++++++++++++++
 t/t7512-status-help.sh             |   1 +
 wt-status.c                        |   3 +
 6 files changed, 203 insertions(+), 15 deletions(-)
base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v16
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v16
Pull-Request: https://github.com/gitgitgadget/git/pull/1076
Range-diff vs v15:
 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  8ec8e212672 ! 2:  b9e03f2342b am: support --empty=<option> to handle empty patches
     @@ Commit message
          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').
     +    valid options ('stop', 'drop' and 'keep').
      
          Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
     @@ Documentation/git-am.txt: SYNOPSIS
       	 [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
       	 [--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
       	 [--quoted-cr=<action>]
     -+	 [--empty=(die|drop|keep)]
     ++	 [--empty=(stop|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=(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.
     ++--empty=(stop|drop|keep)::
     ++	By default, or when the option is set to 'stop', 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.
      +
     @@ builtin/am.c: enum show_patch_type {
       };
       
      +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 */
     ++	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an 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 {
     @@ builtin/am.c: static int am_option_parse_quoted_cr(const struct option *opt,
      +
      +	BUG_ON_OPT_NEG(unset);
      +
     -+	if (!strcmp(arg, "die"))
     -+		*opt_value = DIE_EMPTY_COMMIT;
     ++	if (!strcmp(arg, "stop"))
     ++		*opt_value = STOP_ON_EMPTY_COMMIT;
      +	else if (!strcmp(arg, "drop"))
      +		*opt_value = DROP_EMPTY_COMMIT;
      +	else if (!strcmp(arg, "keep"))
     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
      +			case KEEP_EMPTY_COMMIT:
      +				to_keep = 1;
      +				break;
     -+			case DIE_EMPTY_COMMIT:
     -+				am_destroy(state);
     -+				die(_("Patch is empty."));
     -+				break;
     -+			case ERR_EMPTY_COMMIT:
     ++			case STOP_ON_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(ERR_EMPTY_COMMIT, "empty", &state.empty_type, "{die,drop,keep}",
     ++		OPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,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 and stop in the middle of an am session (default)' '
     ++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 &&
      +	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 is an error where an explicit "--empty=stop" is given' '
     ++	test_when_finished "git am --abort || :" &&
     ++	test_must_fail git am --empty=stop 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' '
 3:  d669406a312 ! 3:  abcdfa1b375 am: support --allow-empty to record specific empty patches
     @@ Commit message
       ## Documentation/git-am.txt ##
      @@ Documentation/git-am.txt: SYNOPSIS
       	 [--quoted-cr=<action>]
     - 	 [--empty=(die|drop|keep)]
     + 	 [--empty=(stop|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)
     @@ 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)
     - 				die(_("Patch is empty."));
     + 				to_keep = 1;
       				break;
     - 			case ERR_EMPTY_COMMIT:
     + 			case STOP_ON_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:
        * 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.
     ++ * is true, commit as an empty commit when index had not changed and lacking a patch.
        */
      -static void am_resolve(struct am_state *state)
      +static void am_resolve(struct am_state *state, int allow_empty)
     @@ builtin/am.c: next:
      -		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."));
     -+	/**
     -+	 * "--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."));
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 05:01 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 05:01 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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 ('stop', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 5 deletions(-)
Show changes to 3 files +108 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..7676bd58ae7 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=(stop|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=(stop|drop|keep)::
+	By default, or when the option is set to 'stop', 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..f45aa33366f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an 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 +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, "stop"))
+		*opt_value = STOP_ON_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 STOP_ON_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(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,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..ce8e8e79ace 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=stop" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=stop 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
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 05:01 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

This option helps to record specific empty patches in the middle 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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  7 +++++-
 builtin/am.c             | 37 +++++++++++++++++++++-------
 t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 91 insertions(+), 10 deletions(-)
Show changes to 5 files +91 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 7676bd58ae7..8d3aa552a20 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(stop|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 f45aa33366f..05cbb42dd12 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 STOP_ON_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,21 +1899,34 @@ 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 index had not changed and lacking a patch.
  */
-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)) {
-		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."));
+	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 {
+			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()) {
 		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"
@@ -2237,7 +2251,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2390,6 +2405,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 +2516,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 ce8e8e79ace..e154b4201b7 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1201,4 +1201,57 @@ 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_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
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
Aleen via GitGitGadget· Dec 7, 2021, 08:31 UTC · re: Aleen via GitGitGadget · lore

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

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
----------------------------------------------------------------------------
Changes since v15:
 1. rename "die" to "stop".
----------------------------------------------------------------------------
Changes since v16:
 1. fix typo from "had" to "has" in the comment.
徐沛文 (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                       |  90 +++++++++++++++++++++----
 t/t4150-am.sh                      | 102 +++++++++++++++++++++++++++++
 t/t7512-status-help.sh             |   1 +
 wt-status.c                        |   3 +
 6 files changed, 203 insertions(+), 15 deletions(-)
base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v17
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v17
Pull-Request: https://github.com/gitgitgadget/git/pull/1076
Range-diff vs v16:
 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  b9e03f2342b = 2:  b9e03f2342b am: support --empty=<option> to handle empty patches
 3:  abcdfa1b375 ! 3:  ea2dc088b37 am: support --allow-empty to record specific empty patches
     @@ 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 when index had not changed and lacking a patch.
     ++ * is true, commit as an empty commit when index has not changed and lacking a patch.
        */
      -static void am_resolve(struct am_state *state)
      +static void am_resolve(struct am_state *state, int allow_empty)
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 08:31 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 08:31 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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 ('stop', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 55 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 49 +++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 5 deletions(-)
Show changes to 3 files +108 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..7676bd58ae7 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=(stop|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=(stop|drop|keep)::
+	By default, or when the option is set to 'stop', 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..f45aa33366f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an 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 +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, "stop"))
+		*opt_value = STOP_ON_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 STOP_ON_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(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,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..ce8e8e79ace 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=stop" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=stop 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
Junio C Hamano· Dec 7, 2021, 18:12 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

""徐沛文 (Aleen)" via GitGitGadget" <gitgitgadget@gmail.com> writes:

This step look mostly good and well done, except for just a few things that remain.

Show 5 quoted lines
> +enum empty_action {
> +	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */
> +	DROP_EMPTY_COMMIT,         /* skip with a notice message, unless "--quiet" has been passed */
> +	KEEP_EMPTY_COMMIT          /* keep recording as empty commits */
> +};

It is friendly to future developers to end the last item in enum with a comma, unless the current last item MUST stay to be the last one even when they add new ones. I.e.

	KEEP_EMPTY_COMMIT,          /* keep recording as empty commits */
Show 8 quoted lines
>  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 */

Mental note. After this series graduates to 'master', at some point in the future, we should clean these members up to be of their respective enum types, not "int".

Show 21 quoted lines
> @@ -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;

This causes the code that produces the "Applying" message jumped over, so the user will not see anything done for this step.

I think we want to mimic the above case arm and do something like
				say(state, stdout, _("Creating an empty commit: %.*s"),
					linelen(state->msg), state->msg);
to avoid being mum about what was done in this step.
Show 23 quoted lines
> +				break;
> +			case STOP_ON_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:
Show 5 quoted lines
> +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 &&

For the test data prepared by the earlier part of this patch, this does not make a difference, but by using %B instead of %s, I think you can catch a future bug that only keeps the subject intact while munging the body of the message.

Other than these, looking quite good.
Thanks.
徐沛文 (Aleen) via GitGitGadget· Dec 7, 2021, 08:31 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

This option helps to record specific empty patches in the middle 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) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  7 +++++-
 builtin/am.c             | 37 +++++++++++++++++++++-------
 t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  3 +++
 5 files changed, 91 insertions(+), 10 deletions(-)
Show changes to 5 files +91 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 7676bd58ae7..8d3aa552a20 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(stop|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 f45aa33366f..89a29ccac89 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 STOP_ON_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,21 +1899,34 @@ 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 index has not changed and lacking a patch.
  */
-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)) {
-		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."));
+	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 {
+			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()) {
 		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"
@@ -2237,7 +2251,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY
 };
 
 struct resume_mode {
@@ -2390,6 +2405,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 +2516,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 ce8e8e79ace..e154b4201b7 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1201,4 +1201,57 @@ 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_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
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
Junio C Hamano· Dec 7, 2021, 18:23 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

""徐沛文 (Aleen)" via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 39 quoted lines
> From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
>  <aleen42@vip.qq.com>
>
> This option helps to record specific empty patches in the middle
> 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) <aleen42@vip.qq.com>
> ---
>  Documentation/git-am.txt |  7 +++++-
>  builtin/am.c             | 37 +++++++++++++++++++++-------
>  t/t4150-am.sh            | 53 ++++++++++++++++++++++++++++++++++++++++
>  t/t7512-status-help.sh   |  1 +
>  wt-status.c              |  3 +++
>  5 files changed, 91 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
> index 7676bd58ae7..8d3aa552a20 100644
> --- a/Documentation/git-am.txt
> +++ b/Documentation/git-am.txt
> @@ -18,7 +18,7 @@ SYNOPSIS
>  	 [--quoted-cr=<action>]
>  	 [--empty=(stop|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.

"The user can still do X" is not technically incorrect, but this is not merely a possiblity but does actively does X, so

	After a patch failure on an input e-mail message lacking a patch,
	create an empty commit with the contents of the e-mail
	message as its log message.
or something.
Show 11 quoted lines
> diff --git a/builtin/am.c b/builtin/am.c
> index f45aa33366f..89a29ccac89 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 STOP_ON_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\"."));

It would be better to hide the new part of the message behind advice_if_enabled(), and advise "--skip" first before giving "--allow-empty" as another possibility. It is rather a common mistake to grab patches with the cover letter and see "am" die on the cover letter.

If we were to add an advice message there, that is. I am not sure if it is necessary, especially since we give them in "git status".

Show 23 quoted lines
> @@ -1898,21 +1899,34 @@ 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 index has not changed and lacking a patch.
>   */
> -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)) {
> -		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."));
> +	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
> +	if (allow_empty && (index_changed || !is_empty_or_missing_file(am_path(state, "patch")))) {
Overlong line.
> +		printf_ln(_("Invalid resume value."));

It is unclear to the user what "resume value" means. I am guessing that this is saying that the user gave us the "--allow-empty" option, but it is an inappropriate option for the situation. So

	"--allow-empty is not appropriate in this situation"

would be a slight improvement, but it is not clear what makes it inappropriate, iow, what the situation is, to the users.

Stepping back a bit, if we disect that overlong condition:
	if (allow_empty &&
	    (index_changed || !is_empty_or_missing_file(am_path(state, "patch")))) {

it seems to be computing something not quite right. What we want with "allow" empty is (1) if we have recorded changes to the index, that is perfectly fine. We'll create a non-empty commit, and (2) if we do not have any change in the index because the input did not have a patch, it is OK to create an empty commit out of the current index. But the condition is there only to complain about "--allow-empty" given when either (1) we have changes, or (2) when the input had a patch. These are conditions that we can internally ignore allow_empty settings and let the existing code handle the message as before this patch.

>  		die_user_resolve(state);

And I do not see how "when you have resolved with this problem, do one of these things" message would help in this situation. Perhaps we can get rid of the above block. We still need to compute index_changed, and make "no changes--we will die" conditional.

> +	if (!index_changed) {
> +		if (allow_empty)
> +			printf_ln(_("No changes - record it as an empty commit."));

A command to the user to "record it as an empty commit" is not what we want to give. Perhaps "recorded it as ..." would work better as a report of what we did for the user.

Also style:
		if (allow_empty) {
			printf_ln(...);
		} else {
                	... original multi-statement block come here ...
		}

That is, when one arm of if/else if/.../else cascade needs {}, put {} to all of them.

I think there is a logic error here once we remove the way too aggressive "you may not say --allow-empty when there is a patch" check we saw earlier.

When we see no changes added to the index and "--allow-empty" given, we shouldn't be blindly creating an empty commit without checking if we didn't have any patch. If the last failure that gave the control back from "am" to the user was a patch that did not apply, we do not want to create an empty commit, do we? So something like

	if (!repo_index_has_changes(the_repository, NULL, NULL)) {
		if (allow_empty &&
		    is_empty_or_missing_file(am_path(state, "patch"))) {
			printf_ln(_("No changes - recorded an empty commit."));
		} else {
			... original block ...
perhaps?
Show 7 quoted lines
> +		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);
> +		}
> +	}
Show 7 quoted lines
> @@ -2237,7 +2251,8 @@ enum resume_type {
>  	RESUME_SKIP,
>  	RESUME_ABORT,
>  	RESUME_QUIT,
> -	RESUME_SHOW_PATCH
> +	RESUME_SHOW_PATCH,
> +	RESUME_ALLOW_EMPTY
Advice on the trailing comma applies here equally.
>  };
Show 11 quoted lines
> 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)

You do not want to add the new one the first. "--skip" should come first, and perhaps this new one, and "--abort" at the end.

The general rule of thumb is to give common ones early before the less common ones and ones, and give ones with more severe consequence the last.

Show 14 quoted lines
> 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,
Junio C Hamano· Dec 7, 2021, 18:24 UTC · re: Aleen via GitGitGadget · lore

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

"Aleen via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 7 quoted lines
> 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 v16:
>
>  1. fix typo from "had" to "has" in the comment.

I read the patches in full again and saw some issues that want to be fixed, but overall it was a pleasant read.

Thanks.
Aleen via GitGitGadget· Dec 8, 2021, 05:05 UTC · re: Aleen via GitGitGadget · lore

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

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
----------------------------------------------------------------------------
Changes since v15:
 1. rename "die" to "stop".
----------------------------------------------------------------------------
Changes since v16:
 1. fix the typo from "had" to "has" in the comment.
----------------------------------------------------------------------------
Changes since v17:
 1. add trailing comma, show tips of creating an empty commit, and use "%B"
    to construct test cases.
 2. remove the error "Invalid resume value.".
 3. hint "--allow-empty" after "--skip".
徐沛文 (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                       |  96 ++++++++++++++++++++++----
 t/t4150-am.sh                      | 104 +++++++++++++++++++++++++++++
 t/t7512-status-help.sh             |   1 +
 wt-status.c                        |   8 ++-
 6 files changed, 215 insertions(+), 16 deletions(-)
base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v18
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v18
Pull-Request: https://github.com/gitgitgadget/git/pull/1076
Range-diff vs v17:
 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  b9e03f2342b ! 2:  56953d416d9 am: support --empty=<option> to handle empty patches
     @@ builtin/am.c: enum show_patch_type {
      +enum empty_action {
      +	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an am session */
      +	DROP_EMPTY_COMMIT,         /* skip with a notice message, unless "--quiet" has been passed */
     -+	KEEP_EMPTY_COMMIT          /* keep recording as empty commits */
     ++	KEEP_EMPTY_COMMIT,         /* keep recording as empty commits */
      +};
      +
       struct am_state {
     @@ builtin/am.c: static void am_run(struct am_state *state, int resume)
      +				break;
      +			case KEEP_EMPTY_COMMIT:
      +				to_keep = 1;
     ++				say(state, stdout, _("Creating an empty commit: %.*s"),
     ++					linelen(state->msg), state->msg);
      +				break;
      +			case STOP_ON_EMPTY_COMMIT:
      +				printf_ln(_("Patch is empty."));
     @@ t/t4150-am.sh: test_expect_success 'apply binary blob in partial clone' '
      +'
      +
      +test_expect_success 'record as an empty commit when meeting e-mail message that lacks a patch' '
     -+	git am --empty=keep empty-commit.patch &&
     ++	git am --empty=keep empty-commit.patch >output &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git show empty-commit --format="%s" >expected &&
     -+	git show HEAD --format="%s" >actual &&
     -+	test_cmp actual expected
     ++	git show empty-commit --format="%B" >expected &&
     ++	git show HEAD --format="%B" >actual &&
     ++	grep -f actual expected &&
     ++	grep "Creating an empty commit: empty commit" output
      +'
      +
       test_done
 3:  ea2dc088b37 ! 3:  4c3077f9384 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. However, it is a valid resume value only when:
     +    of an am session, which does create empty commits only when:
      
              1. index has not changed
              2. lacking a branch
     @@ Documentation/git-am.txt: default.   You can use `--no-utf8` to override this.
       
      +--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.
     ++	create an empty commit with the contents of the e-mail message
     ++	as its log message.
      +
       DISCUSSION
       ----------
       
      
       ## builtin/am.c ##
     -@@ builtin/am.c: static void am_run(struct am_state *state, int resume)
     - 				to_keep = 1;
     - 				break;
     - 			case STOP_ON_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;
     - 			}
     +@@ builtin/am.c: static void NORETURN die_user_resolve(const struct am_state *state)
     + 
     + 		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
     + 		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
     ++
     ++		if (advice_enabled(ADVICE_AM_WORK_DIR) &&
     ++		    is_empty_or_missing_file(am_path(state, "patch")) &&
     ++		    !repo_index_has_changes(the_repository, NULL, NULL))
     ++			printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline);
     ++
     + 		printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
     + 	}
     + 
      @@ builtin/am.c: next:
       /**
        * Resume the current am session after patch application failure. The user did
     @@ builtin/am.c: next:
      -			"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."));
      +	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."));
     ++	if (allow_empty &&
     ++	    !(!index_changed && is_empty_or_missing_file(am_path(state, "patch"))))
       		die_user_resolve(state);
     - 	}
     - 
     ++
      +	if (!index_changed) {
     -+		if (allow_empty)
     -+			printf_ln(_("No changes - record it as an empty commit."));
     -+		else {
     ++		if (allow_empty) {
     ++			printf_ln(_("No changes - recorded 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()) {
     - 		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,
       	RESUME_QUIT,
      -	RESUME_SHOW_PATCH
      +	RESUME_SHOW_PATCH,
     -+	RESUME_ALLOW_EMPTY
     ++	RESUME_ALLOW_EMPTY,
       };
       
       struct resume_mode {
     @@ builtin/am.c: int cmd_am(int argc, const char **argv, const char *prefix)
      
       ## t/t4150-am.sh ##
      @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mail message that
     - 	test_cmp actual expected
     + 	grep "Creating an empty commit: empty commit" output
       '
       
      +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 &&
     ++	grep "To record the empty patch 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 &&
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	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 &&
     ++	grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
     ++	git am --allow-empty >output &&
     ++	grep "No changes - recorded it as an empty commit." output &&
      +	test_path_is_missing .git/rebase-apply &&
     -+	git show empty-commit --format="%s" >expected &&
     -+	git show HEAD --format="%s" >actual &&
     -+	test_cmp actual expected
     ++	git show empty-commit --format="%B" >expected &&
     ++	git show HEAD --format="%B" >actual &&
     ++	grep -f actual expected
      +'
      +
      +test_expect_success 'cannot create empty commits when the index is changed' '
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	: >empty-file &&
      +	git add empty-file &&
      +	test_must_fail git am --allow-empty >err &&
     -+	grep "Invalid resume value." err
     ++	! grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err
      +'
      +
      +test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	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 &&
     ++	! grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
      +	git rev-parse HEAD >actual &&
      +	test_cmp actual expected
      +'
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	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 &&
     ++	! grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
      +	git rev-parse HEAD >actual &&
      +	test_cmp actual expected
      +'
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
       test_done
      
       ## t/t7512-status-help.sh ##
     -@@ t/t7512-status-help.sh: test_expect_success 'status in an am session: empty patch' '
     - On branch am_empty
     +@@ t/t7512-status-help.sh: 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 --allow-empty" to record this patch as an empty commit)
         (use "git am --abort" to restore the original branch)
       
     + nothing to commit (use -u to show untracked files)
      
       ## wt-status.c ##
     -@@ wt-status.c: static void show_am_in_progress(struct wt_status *s,
     - 		if (!s->state.am_empty_patch)
     +@@ wt-status.c: static void show_merge_in_progress(struct wt_status *s,
     + static void show_am_in_progress(struct wt_status *s,
     + 				const char *color)
     + {
     ++	int am_empty_patch;
     ++
     + 	status_printf_ln(s, color,
     + 		_("You are in the middle of an am session."));
     + 	if (s->state.am_empty_patch)
     + 		status_printf_ln(s, color,
     + 			_("The current patch is empty."));
     + 	if (s->hints) {
     +-		if (!s->state.am_empty_patch)
     ++		am_empty_patch = s->state.am_empty_patch;
     ++		if (!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)"));
     ++		if (am_empty_patch)
     ++			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 --abort\" to restore the original branch)"));
     + 	}
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 8, 2021, 05:05 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
徐沛文 (Aleen) via GitGitGadget· Dec 8, 2021, 05:05 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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 ('stop', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 57 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++++
 3 files changed, 111 insertions(+), 5 deletions(-)
Show changes to 3 files +111 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..7676bd58ae7 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=(stop|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=(stop|drop|keep)::
+	By default, or when the option is set to 'stop', 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..7a66ad23737 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an 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 +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, "stop"))
+		*opt_value = STOP_ON_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,29 @@ 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;
+				say(state, stdout, _("Creating an empty commit: %.*s"),
+					linelen(state->msg), state->msg);
+				break;
+			case STOP_ON_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 +1870,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2401,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(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,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..f2a7a68eda0 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 (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=stop" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=stop 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 >output &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected &&
+	grep "Creating an empty commit: empty commit" output
+'
+
 test_done
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 8, 2021, 05:05 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

This option helps to record specific empty patches in the middle of an am session, which does create empty commits only when:

    1. index has not changed
    2. lacking a branch
Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  7 +++++-
 builtin/am.c             | 39 +++++++++++++++++++++++------
 t/t4150-am.sh            | 54 ++++++++++++++++++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  8 +++++-
 5 files changed, 99 insertions(+), 10 deletions(-)
Show changes to 5 files +99 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 7676bd58ae7..09107fb1067 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(stop|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,
+	create an empty commit with the contents of the e-mail message
+	as its log message.
+
 DISCUSSION
 ----------
 
diff --git a/builtin/am.c b/builtin/am.c
index 7a66ad23737..d73e415bbb0 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)
 
 		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
 		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+
+		if (advice_enabled(ADVICE_AM_WORK_DIR) &&
+		    is_empty_or_missing_file(am_path(state, "patch")) &&
+		    !repo_index_has_changes(the_repository, NULL, NULL))
+			printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline);
+
 		printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
 	}
 
@@ -1900,19 +1906,31 @@ 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 index has not changed and lacking a patch.
  */
-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)) {
-		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."));
+	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
+	if (allow_empty &&
+	    !(!index_changed && is_empty_or_missing_file(am_path(state, "patch"))))
 		die_user_resolve(state);
+
+	if (!index_changed) {
+		if (allow_empty) {
+			printf_ln(_("No changes - recorded 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()) {
@@ -2239,7 +2257,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY,
 };
 
 struct resume_mode {
@@ -2392,6 +2411,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")),
@@ -2500,7 +2522,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 f2a7a68eda0..6b23384685b 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1202,4 +1202,58 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
 	grep "Creating an empty commit: empty commit" output
 '
 
+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 "To record the empty patch 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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --allow-empty >output &&
+	grep "No changes - recorded it as an empty commit." output &&
+	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_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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." 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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." 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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
+	git rev-parse HEAD >actual &&
+	test_cmp actual expected
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 7f2956d77ad..2f16d5787ed 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -659,6 +659,7 @@ On branch am_empty
 You are in the middle of an am session.
 The current patch is empty.
   (use "git am --skip" to skip this patch)
+  (use "git am --allow-empty" to record this patch as an empty commit)
   (use "git am --abort" to restore the original branch)
 
 nothing to commit (use -u to show untracked files)
diff --git a/wt-status.c b/wt-status.c
index 5d215f4e4f1..335e723a71e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1218,17 +1218,23 @@ static void show_merge_in_progress(struct wt_status *s,
 static void show_am_in_progress(struct wt_status *s,
 				const char *color)
 {
+	int am_empty_patch;
+
 	status_printf_ln(s, color,
 		_("You are in the middle of an am session."));
 	if (s->state.am_empty_patch)
 		status_printf_ln(s, color,
 			_("The current patch is empty."));
 	if (s->hints) {
-		if (!s->state.am_empty_patch)
+		am_empty_patch = s->state.am_empty_patch;
+		if (!am_empty_patch)
 			status_printf_ln(s, color,
 				_("  (fix conflicts and then run \"git am --continue\")"));
 		status_printf_ln(s, color,
 			_("  (use \"git am --skip\" to skip this patch)"));
+		if (am_empty_patch)
+			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 --abort\" to restore the original branch)"));
 	}
-- 
gitgitgadget
Junio C Hamano· Dec 8, 2021, 06:22 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

""徐沛文 (Aleen)" via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 10 quoted lines
> @@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)
>  
>  		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
>  		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
> +
> +		if (advice_enabled(ADVICE_AM_WORK_DIR) &&
> +		    is_empty_or_missing_file(am_path(state, "patch")) &&
> +		    !repo_index_has_changes(the_repository, NULL, NULL))
> +			printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline);
> +

OK, so the idea is when we give back control to the user, if the reason we stopped is because we did not have a usable patch in the message and we didn't have any change to the index, we can offer "--allow-empty" as an extra choice.

I guess that makes sense.
Show 21 quoted lines
> @@ -1900,19 +1906,31 @@ 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 index has not changed and lacking a patch.
>   */
> -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)) {
> -		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."));
> +	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
> +	if (allow_empty &&
> +	    !(!index_changed && is_empty_or_missing_file(am_path(state, "patch"))))
>  		die_user_resolve(state);
Why do we need this?  

Intuitively, because "--allow-XYZ" is "we normally die when the condition XYZ holds, but with the option, we do not die in such a case", any new condition that leads to "die" that only kicks in when "--allow-XYZ" is given smell like a BUG.

The condition reads:
    unless we saw an empty patch (i.e. "patch" file is missing or
    empty, and did not result in any change to the index), it is an
    error to give "--allow-empty" to the command.

That somehow does not make any sense to me. Whether failed patch application and manual tweaking resulted in the same tree as HEAD, or a tree different from HEAD, if the user wants to say "I allow Git to create an empty commit as necessary, instead of stopping", shouldn't the user be allowed to? After all, the option is not "create nothing but an empty commit, it is an error if there is something to commit." It merely allows creation of an empty one.

This series has been trying to add code to punish users who give "--allow-empty" when the command would have worked the same way without it at least since the last round, and I am not sure where that wish to stop users comes from. Please explain. I am starting to think I may be missing something and this extra rigidity may be helping a user (but again, I do not see how).

> +	if (!index_changed) {
> +		if (allow_empty) {

The index_changed variable being false is "the result is an empty change", and is-empty-or-missing on "patch" file is "the input is an empty change". Ideally we would want to create an empty commit only when both input and result are empty, but in order to prevent mistakes, we may want to react to the case where the result is empty but the input is not empty.

Here is where we can use is-empty-or-missing on "patch" and give a different message (i.e. "even though the patch file is not empty, the result of your patch application did not change anything"), if we wanted to. Or you could choose to reject such an attempt, which might be simpler. I.e.

		if (allow_empty &&
		    is_empty_or_missing_file(am_path(state, "patch"))
			"No changes - recorded an empty commit."
		else
			... the original error for no changes ...
Hmm?
Aleen 徐沛文· Dec 8, 2021, 06:46 UTC · re: Junio C Hamano · lore

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

Show 32 quoted lines
> > +	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
> > +	if (allow_empty &&
> > +	    !(!index_changed && is_empty_or_missing_file(am_path(state, "patch"))))
> >  		die_user_resolve(state);
> 
> Why do we need this?  
> 
> Intuitively, because "--allow-XYZ" is "we normally die when the
> condition XYZ holds, but with the option, we do not die in such a
> case", any new condition that leads to "die" that only kicks in
> when "--allow-XYZ" is given smell like a BUG.
> 
> The condition reads:
> 
>     unless we saw an empty patch (i.e. "patch" file is missing or
>     empty, and did not result in any change to the index), it is an
>     error to give "--allow-empty" to the command.
> 
> That somehow does not make any sense to me.  Whether failed patch
> application and manual tweaking resulted in the same tree as HEAD,
> or a tree different from HEAD, if the user wants to say "I allow
> Git to create an empty commit as necessary, instead of stopping",
> shouldn't the user be allowed to?  After all, the option is not
> "create nothing but an empty commit, it is an error if there is
> something to commit."  It merely allows creation of an empty one.
> 
> This series has been trying to add code to punish users who give
> "--allow-empty" when the command would have worked the same way
> without it at least since the last round, and I am not sure where
> that wish to stop users comes from.  Please explain.  I am starting
> to think I may be missing something and this extra rigidity may be
> helping a user (but again, I do not see how).

If there is no such code to check, the user can use "--allow-empty" to do the same thing as "--continue" or "--resolved" after resolving conflicts because they all go into `am_resolve()`. In my opinion, "--allow-empty" only makes sense to allow the user to create empty commits when:

    1. the result is an empty change (`!index_changed`)
    2. the input is an empty change (`is_empty_or_missing_file(am_path(state, "patch"))`)
Show 24 quoted lines
> > +	if (!index_changed) {
> > +		if (allow_empty) {
> 
> The index_changed variable being false is "the result is an empty
> change", and is-empty-or-missing on "patch" file is "the input is an
> empty change".  Ideally we would want to create an empty commit only
> when both input and result are empty, but in order to prevent mistakes,
> we may want to react to the case where the result is empty but the
> input is not empty.
> 
> Here is where we can use is-empty-or-missing on "patch" and give a
> different message (i.e. "even though the patch file is not empty,
> the result of your patch application did not change anything"), if
> we wanted to.  Or you could choose to reject such an attempt, which
> might be simpler.  I.e.
> 
> 		if (allow_empty &&
> 		    is_empty_or_missing_file(am_path(state, "patch"))
> 			"No changes - recorded an empty commit."
> 		else
> 			... the original error for no changes ...
> 
> 
> Hmm?

Based on the previous checking, there is no need to check `is_empty_or_missing_file(am_path(state, "patch"))` here because it is permanently true.

Junio C Hamano· Dec 8, 2021, 11:32 UTC · re: Aleen 徐沛文 · lore

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

Aleen 徐沛文 <pwxu@coremail.cn> writes:
Show 11 quoted lines
>> This series has been trying to add code to punish users who give
>> "--allow-empty" when the command would have worked the same way
>> without it at least since the last round, and I am not sure where
>> that wish to stop users comes from.  Please explain.  I am starting
>> to think I may be missing something and this extra rigidity may be
>> helping a user (but again, I do not see how).
>
> If there is no such code to check, the user can use
> "--allow-empty" to do the same thing as "--continue" or
> "--resolved" after resolving conflicts because they all go into
> `am_resolve()`.  

Yes, exactly. That is the point of "allow"; we continue normally, and even in a narrow situation (i.e. input is empty, and no change is made to the index) where we normally stop, we allow it to continue by creating an empty commit.

Show 5 quoted lines
> In my opinion, "--allow-empty" only makes sense
> to allow the user to create empty commits when:
>
>     1. the result is an empty change (`!index_changed`)
>     2. the input is an empty change (`is_empty_or_missing_file(am_path(state, "patch"))`)

Then you are forcing the user to use it ONLY whe nhe input is empty and no other time. Whatever that is, that is not "allow" empty.

Show 17 quoted lines
>> Here is where we can use is-empty-or-missing on "patch" and give a
>> different message (i.e. "even though the patch file is not empty,
>> the result of your patch application did not change anything"), if
>> we wanted to.  Or you could choose to reject such an attempt, which
>> might be simpler.  I.e.
>> 
>> 		if (allow_empty &&
>> 		    is_empty_or_missing_file(am_path(state, "patch"))
>> 			"No changes - recorded an empty commit."
>> 		else
>> 			... the original error for no changes ...
>> 
>> 
>> Hmm?
>
> Based on the previous checking, there is no need to check
> `is_empty_or_missing_file(am_path(state, "patch"))` here because it is permanently true.
I think you misunderstood.

If we correct the overly strict check that makes "--allow-empty" to mean "only create empty, anything else is an error", the "is patch an empty file?" must be checked here.

Aleen via GitGitGadget· Dec 9, 2021, 07:25 UTC · re: Aleen via GitGitGadget · lore

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

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
----------------------------------------------------------------------------
Changes since v15:
 1. rename "die" to "stop".
----------------------------------------------------------------------------
Changes since v16:
 1. fix the typo from "had" to "has" in the comment.
----------------------------------------------------------------------------
Changes since v17:
 1. add trailing comma, show tips of creating an empty commit, and use "%B"
    to construct test cases.
 2. remove the error "Invalid resume value.".
 3. hint "--allow-empty" after "--skip".
----------------------------------------------------------------------------
Changes since v18:
 1. remove strict checking of "--allow-empty".
徐沛文 (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                       |  89 ++++++++++++++++++++----
 t/t4150-am.sh                      | 107 +++++++++++++++++++++++++++++
 t/t7512-status-help.sh             |   1 +
 wt-status.c                        |   8 ++-
 6 files changed, 211 insertions(+), 16 deletions(-)
base-commit: abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1076%2Faleen42%2Fnext-v19
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1076/aleen42/next-v19
Pull-Request: https://github.com/gitgitgadget/git/pull/1076
Range-diff vs v18:
 1:  a524ca6adfa = 1:  a524ca6adfa doc: git-format-patch: describe the option --always
 2:  56953d416d9 = 2:  56953d416d9 am: support --empty=<option> to handle empty patches
 3:  4c3077f9384 ! 3:  37875e8d313 am: support --allow-empty to record specific empty patches
     @@ Commit message
          This option helps to record specific empty patches in the middle
          of an am session, which does create empty commits only when:
      
     -        1. index has not changed
     +        1. the index has not changed
              2. lacking a branch
      
     +    When the index has changed, "--allow-empty" will create a non-empty
     +    commit like passing "--continue" or "--resolved".
     +
          Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
      
       ## Documentation/git-am.txt ##
     @@ 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."));
     -+	index_changed = repo_index_has_changes(the_repository, NULL, NULL);
     -+	if (allow_empty &&
     -+	    !(!index_changed && is_empty_or_missing_file(am_path(state, "patch"))))
     - 		die_user_resolve(state);
     -+
     -+	if (!index_changed) {
     -+		if (allow_empty) {
     +-		die_user_resolve(state);
     ++		if (allow_empty && is_empty_or_missing_file(am_path(state, "patch"))) {
      +			printf_ln(_("No changes - recorded it as an empty commit."));
      +		} else {
      +			printf_ln(_("No changes - did you forget to use 'git add'?\n"
     @@ t/t4150-am.sh: test_expect_success 'record as an empty commit when meeting e-mai
      +	grep -f actual expected
      +'
      +
     -+test_expect_success 'cannot create empty commits when the index is changed' '
     ++test_expect_success 'create an non-empty commit when the index IS changed though "--allow-empty" is given' '
      +	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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err
     ++	git am --allow-empty &&
     ++	git show empty-commit --format="%B" >expected &&
     ++	git show HEAD --format="%B" >actual &&
     ++	grep -f actual expected &&
     ++	git diff HEAD^..HEAD --name-only
      +'
      +
      +test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 9, 2021, 07:25 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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) <aleen42@vip.qq.com>
---
 Documentation/git-format-patch.txt | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
Show changes to Documentation/git-format-patch.txt +5 −1
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
Bagas Sanjaya· Dec 9, 2021, 09:28 UTC · re: 徐沛文 (Aleen) via GitGitGadget · lore

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

On 09/12/21 14.25, 徐沛文 (Aleen) via GitGitGadget wrote:
> +--always::
> +	Include patches for commits that do not introduce any change,
> +	which are omitted by default.
> +
What about this wording below?
Show changes to Documentation/git-format-patch.txt +2 −2
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index be797d7a28..302659cfd5 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -193,8 +193,8 @@ will want to ensure that threading is disabled for `git send-email`.
  	ignored.
  
  --always::
-	Include patches for commits that do not introduce any change,
-	which are omitted by default.
+	Always generate patches, even if there are empty-change commits.
+	Default is to omit such commits.
  
  --cover-from-description=<mode>::
  	Controls which parts of the cover letter will be automatically
-- 
An old man doll... just what I always wanted! - Clara
Aleen 徐沛文· Dec 10, 2021, 01:26 UTC · re: Bagas Sanjaya · lore

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

Show 21 quoted lines
> What about this wording below?
> 
> diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
> index be797d7a28..302659cfd5 100644
> --- a/Documentation/git-format-patch.txt
> +++ b/Documentation/git-format-patch.txt
> @@ -193,8 +193,8 @@ will want to ensure that threading is disabled for `git send-email`.
>   	ignored.
>   
>   --always::
> -	Include patches for commits that do not introduce any change,
> -	which are omitted by default.
> +	Always generate patches, even if there are empty-change commits.
> +	Default is to omit such commits.
>   
>   --cover-from-description=<mode>::
>   	Controls which parts of the cover letter will be automatically
> 
> 
> -- 
> An old man doll... just what I always wanted! - Clara
Dears Sanjaya,
    Thanks for your suggestion. I don't see the actual difference between
    the two sentences, and do you want to enhance the word "always"?
    If you do, how about just describing the option as "Always include patches..."?
Aleen
Bagas Sanjaya· Dec 10, 2021, 06:50 UTC · re: Aleen 徐沛文 · lore

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

On 10/12/21 08.26, Aleen 徐沛文 wrote:
Show 8 quoted lines
> Dears Sanjaya,
> 
>      Thanks for your suggestion. I don't see the actual difference between
>      the two sentences, and do you want to enhance the word "always"?
>      If you do, how about just describing the option as "Always include patches..."?
> 
> Aleen
> 

Semantically speaking, I choose "generate" because git format-patch **generates** patches from the specified commits.

-- 
An old man doll... just what I always wanted! - Clara
Junio C Hamano· Dec 11, 2021, 09:22 UTC · re: Bagas Sanjaya · lore

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

Bagas Sanjaya <bagasdotme@gmail.com> writes:
Show 11 quoted lines
> On 10/12/21 08.26, Aleen 徐沛文 wrote:
>> Dears Sanjaya,
>>      Thanks for your suggestion. I don't see the actual difference
>> between
>>      the two sentences, and do you want to enhance the word "always"?
>>      If you do, how about just describing the option as "Always include patches..."?
>> Aleen
>> 
>
> Semantically speaking, I choose "generate" because git format-patch **generates**
> patches from the specified commits.

But the given commit is empty and does not result in a patch at all, which is the whole point of having this option, no? A typical format-patch output message consists of the message part and the patch part, and calling an output created for an empty commit a "patch" leads to confusion, I am afraid.

So, between the two sentences, I do not see much difference either way.

徐沛文 (Aleen) via GitGitGadget· Dec 9, 2021, 07:25 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

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 ('stop', 'drop' and 'keep').

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  9 +++++++
 builtin/am.c             | 57 ++++++++++++++++++++++++++++++++++++----
 t/t4150-am.sh            | 50 +++++++++++++++++++++++++++++++++++
 3 files changed, 111 insertions(+), 5 deletions(-)
Show changes to 3 files +111 −5

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 0a4a984dfde..7676bd58ae7 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=(stop|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=(stop|drop|keep)::
+	By default, or when the option is set to 'stop', 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..7a66ad23737 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -87,6 +87,12 @@ enum show_patch_type {
 	SHOW_PATCH_DIFF = 1,
 };
 
+enum empty_action {
+	STOP_ON_EMPTY_COMMIT = 0,  /* output errors and stop in the middle of an 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 +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, "stop"))
+		*opt_value = STOP_ON_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,29 @@ 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;
+				say(state, stdout, _("Creating an empty commit: %.*s"),
+					linelen(state->msg), state->msg);
+				break;
+			case STOP_ON_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 +1870,7 @@ static void am_run(struct am_state *state, int resume)
 			die_user_resolve(state);
 		}
 
+commit:
 		do_commit(state);
 
 next:
@@ -2357,6 +2401,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(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,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..f2a7a68eda0 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 (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=stop" is given' '
+	test_when_finished "git am --abort || :" &&
+	test_must_fail git am --empty=stop 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 >output &&
+	test_path_is_missing .git/rebase-apply &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected &&
+	grep "Creating an empty commit: empty commit" output
+'
+
 test_done
-- 
gitgitgadget
徐沛文 (Aleen) via GitGitGadget· Dec 9, 2021, 07:25 UTC · re: Aleen via GitGitGadget · lore

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

From: =?UTF-8?q?=E5=BE=90=E6=B2=9B=E6=96=87=20=28Aleen=29?=
 <aleen42@vip.qq.com>

This option helps to record specific empty patches in the middle of an am session, which does create empty commits only when:

    1. the index has not changed
    2. lacking a branch

When the index has changed, "--allow-empty" will create a non-empty commit like passing "--continue" or "--resolved".

Signed-off-by: 徐沛文 (Aleen) <aleen42@vip.qq.com>
---
 Documentation/git-am.txt |  7 ++++-
 builtin/am.c             | 32 ++++++++++++++++------
 t/t4150-am.sh            | 57 ++++++++++++++++++++++++++++++++++++++++
 t/t7512-status-help.sh   |  1 +
 wt-status.c              |  8 +++++-
 5 files changed, 95 insertions(+), 10 deletions(-)
Show changes to 5 files +95 −10

Documentation/git-am.txt, builtin/am.c, t/t4150-am.sh, t/t7512-status-help.sh, wt-status.c

diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 7676bd58ae7..09107fb1067 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -18,7 +18,7 @@ SYNOPSIS
 	 [--quoted-cr=<action>]
 	 [--empty=(stop|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,
+	create an empty commit with the contents of the e-mail message
+	as its log message.
+
 DISCUSSION
 ----------
 
diff --git a/builtin/am.c b/builtin/am.c
index 7a66ad23737..f2e7e533881 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)
 
 		printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
 		printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+
+		if (advice_enabled(ADVICE_AM_WORK_DIR) &&
+		    is_empty_or_missing_file(am_path(state, "patch")) &&
+		    !repo_index_has_changes(the_repository, NULL, NULL))
+			printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline);
+
 		printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
 	}
 
@@ -1900,19 +1906,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 index has not changed and lacking a patch.
  */
-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 && is_empty_or_missing_file(am_path(state, "patch"))) {
+			printf_ln(_("No changes - recorded 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()) {
@@ -2239,7 +2250,8 @@ enum resume_type {
 	RESUME_SKIP,
 	RESUME_ABORT,
 	RESUME_QUIT,
-	RESUME_SHOW_PATCH
+	RESUME_SHOW_PATCH,
+	RESUME_ALLOW_EMPTY,
 };
 
 struct resume_mode {
@@ -2392,6 +2404,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")),
@@ -2500,7 +2515,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 f2a7a68eda0..2db2c5dfe58 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -1202,4 +1202,61 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
 	grep "Creating an empty commit: empty commit" output
 '
 
+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 "To record the empty patch 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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
+	git am --allow-empty >output &&
+	grep "No changes - recorded it as an empty commit." output &&
+	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_expect_success 'create an non-empty commit when the index IS changed though "--allow-empty" is given' '
+	git checkout empty-commit^ &&
+	test_must_fail git am empty-commit.patch >err &&
+	: >empty-file &&
+	git add empty-file &&
+	git am --allow-empty &&
+	git show empty-commit --format="%B" >expected &&
+	git show HEAD --format="%B" >actual &&
+	grep -f actual expected &&
+	git diff HEAD^..HEAD --name-only
+'
+
+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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." 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 "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
+	git rev-parse HEAD >actual &&
+	test_cmp actual expected
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 7f2956d77ad..2f16d5787ed 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -659,6 +659,7 @@ On branch am_empty
 You are in the middle of an am session.
 The current patch is empty.
   (use "git am --skip" to skip this patch)
+  (use "git am --allow-empty" to record this patch as an empty commit)
   (use "git am --abort" to restore the original branch)
 
 nothing to commit (use -u to show untracked files)
diff --git a/wt-status.c b/wt-status.c
index 5d215f4e4f1..335e723a71e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1218,17 +1218,23 @@ static void show_merge_in_progress(struct wt_status *s,
 static void show_am_in_progress(struct wt_status *s,
 				const char *color)
 {
+	int am_empty_patch;
+
 	status_printf_ln(s, color,
 		_("You are in the middle of an am session."));
 	if (s->state.am_empty_patch)
 		status_printf_ln(s, color,
 			_("The current patch is empty."));
 	if (s->hints) {
-		if (!s->state.am_empty_patch)
+		am_empty_patch = s->state.am_empty_patch;
+		if (!am_empty_patch)
 			status_printf_ln(s, color,
 				_("  (fix conflicts and then run \"git am --continue\")"));
 		status_printf_ln(s, color,
 			_("  (use \"git am --skip\" to skip this patch)"));
+		if (am_empty_patch)
+			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 --abort\" to restore the original branch)"));
 	}
-- 
gitgitgadget

← back to recent threads