am: support --allow-empty to record specific empty patches

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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
徐沛文 (Aleen) 2021-12-09 07:25:55 +00:00 committed by Junio C Hamano
parent 7c096b8d61
commit 9e7e41bf19
5 changed files with 95 additions and 10 deletions

View File

@ -18,7 +18,7 @@ SYNOPSIS
[--quoted-cr=<action>] [--quoted-cr=<action>]
[--empty=(stop|drop|keep)] [--empty=(stop|drop|keep)]
[(<mbox> | <Maildir>)...] [(<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 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. the e-mail message; if `diff`, show the diff portion only.
Defaults to `raw`. 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 DISCUSSION
---------- ----------



View File

@ -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(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), 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); 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 * 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 * 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); validate_resume_state(state);


say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); 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 (allow_empty && is_empty_or_missing_file(am_path(state, "patch"))) {
"If there is nothing left to stage, chances are that something else\n" printf_ln(_("No changes - recorded it as an empty commit."));
"already introduced the same changes; you might want to skip this patch.")); } else {
die_user_resolve(state); 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()) { if (unmerged_cache()) {
@ -2239,7 +2250,8 @@ enum resume_type {
RESUME_SKIP, RESUME_SKIP,
RESUME_ABORT, RESUME_ABORT,
RESUME_QUIT, RESUME_QUIT,
RESUME_SHOW_PATCH RESUME_SHOW_PATCH,
RESUME_ALLOW_EMPTY,
}; };


struct resume_mode { struct resume_mode {
@ -2392,6 +2404,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
N_("show the patch being applied"), N_("show the patch being applied"),
PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
parse_opt_show_current_patch, RESUME_SHOW_PATCH }, 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", OPT_BOOL(0, "committer-date-is-author-date",
&state.committer_date_is_author_date, &state.committer_date_is_author_date,
N_("lie about committer 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); am_run(&state, 1);
break; break;
case RESUME_RESOLVED: case RESUME_RESOLVED:
am_resolve(&state); case RESUME_ALLOW_EMPTY:
am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);
break; break;
case RESUME_SKIP: case RESUME_SKIP:
am_skip(&state); am_skip(&state);

View File

@ -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 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 test_done

View File

@ -659,6 +659,7 @@ On branch am_empty
You are in the middle of an am session. You are in the middle of an am session.
The current patch is empty. The current patch is empty.
(use "git am --skip" to skip this patch) (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) (use "git am --abort" to restore the original branch)


nothing to commit (use -u to show untracked files) nothing to commit (use -u to show untracked files)

View File

@ -1212,17 +1212,23 @@ static void show_merge_in_progress(struct wt_status *s,
static void show_am_in_progress(struct wt_status *s, static void show_am_in_progress(struct wt_status *s,
const char *color) const char *color)
{ {
int am_empty_patch;

status_printf_ln(s, color, status_printf_ln(s, color,
_("You are in the middle of an am session.")); _("You are in the middle of an am session."));
if (s->state.am_empty_patch) if (s->state.am_empty_patch)
status_printf_ln(s, color, status_printf_ln(s, color,
_("The current patch is empty.")); _("The current patch is empty."));
if (s->hints) { 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, status_printf_ln(s, color,
_(" (fix conflicts and then run \"git am --continue\")")); _(" (fix conflicts and then run \"git am --continue\")"));
status_printf_ln(s, color, status_printf_ln(s, color,
_(" (use \"git am --skip\" to skip this patch)")); _(" (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, status_printf_ln(s, color,
_(" (use \"git am --abort\" to restore the original branch)")); _(" (use \"git am --abort\" to restore the original branch)"));
} }