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>

徐沛文 (Aleen) committed Dec 9, 2021 at 07:25 UTC 9e7e41bf19ed10469f9adb60669b1699c81b8ea6
5 files changed +95 -10
Documentation/git-am.txt
+6 -1
@@ -18,7 +18,7 @@ SYNOPSIS
18 [--quoted-cr=<action>]
19 [--empty=(stop|drop|keep)]
20 [(<mbox> | <Maildir>)...]
21 -'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
21 +'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)] | --allow-empty)
22
23 DESCRIPTION
24 -----------
@@ -200,6 +200,11 @@ default. You can use `--no-utf8` to override this.
200 the e-mail message; if `diff`, show the diff portion only.
201 Defaults to `raw`.
202
203 +--allow-empty::
204 + After a patch failure on an input e-mail message lacking a patch,
205 + create an empty commit with the contents of the e-mail message
206 + as its log message.
207 +
208 DISCUSSION
209 ----------
210
builtin/am.c
+24 -8
@@ -1152,6 +1152,12 @@ static void NORETURN die_user_resolve(const struct am_state *state)
1152
1153 printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
1154 printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
1155 +
1156 + if (advice_enabled(ADVICE_AM_WORK_DIR) &&
1157 + is_empty_or_missing_file(am_path(state, "patch")) &&
1158 + !repo_index_has_changes(the_repository, NULL, NULL))
1159 + printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline);
1160 +
1161 printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
1162 }
1163
@@ -1900,19 +1906,24 @@ next:
1906 /**
1907 * Resume the current am session after patch application failure. The user did
1908 * all the hard work, and we do not have to do any patch application. Just
1903 - * trust and commit what the user has in the index and working tree.
1909 + * trust and commit what the user has in the index and working tree. If `allow_empty`
1910 + * is true, commit as an empty commit when index has not changed and lacking a patch.
1911 */
1905 -static void am_resolve(struct am_state *state)
1912 +static void am_resolve(struct am_state *state, int allow_empty)
1913 {
1914 validate_resume_state(state);
1915
1916 say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
1917
1918 if (!repo_index_has_changes(the_repository, NULL, NULL)) {
1912 - printf_ln(_("No changes - did you forget to use 'git add'?\n"
1913 - "If there is nothing left to stage, chances are that something else\n"
1914 - "already introduced the same changes; you might want to skip this patch."));
1915 - die_user_resolve(state);
1919 + if (allow_empty && is_empty_or_missing_file(am_path(state, "patch"))) {
1920 + printf_ln(_("No changes - recorded it as an empty commit."));
1921 + } else {
1922 + printf_ln(_("No changes - did you forget to use 'git add'?\n"
1923 + "If there is nothing left to stage, chances are that something else\n"
1924 + "already introduced the same changes; you might want to skip this patch."));
1925 + die_user_resolve(state);
1926 + }
1927 }
1928
1929 if (unmerged_cache()) {
@@ -2239,7 +2250,8 @@ enum resume_type {
2250 RESUME_SKIP,
2251 RESUME_ABORT,
2252 RESUME_QUIT,
2242 - RESUME_SHOW_PATCH
2253 + RESUME_SHOW_PATCH,
2254 + RESUME_ALLOW_EMPTY,
2255 };
2256
2257 struct resume_mode {
@@ -2392,6 +2404,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
2404 N_("show the patch being applied"),
2405 PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
2406 parse_opt_show_current_patch, RESUME_SHOW_PATCH },
2407 + OPT_CMDMODE(0, "allow-empty", &resume.mode,
2408 + N_("record the empty patch as an empty commit"),
2409 + RESUME_ALLOW_EMPTY),
2410 OPT_BOOL(0, "committer-date-is-author-date",
2411 &state.committer_date_is_author_date,
2412 N_("lie about committer date")),
@@ -2500,7 +2515,8 @@ int cmd_am(int argc, const char **argv, const char *prefix)
2515 am_run(&state, 1);
2516 break;
2517 case RESUME_RESOLVED:
2503 - am_resolve(&state);
2518 + case RESUME_ALLOW_EMPTY:
2519 + am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0);
2520 break;
2521 case RESUME_SKIP:
2522 am_skip(&state);
t/t4150-am.sh
+57
@@ -1202,4 +1202,61 @@ test_expect_success 'record as an empty commit when meeting e-mail message that
1202 grep "Creating an empty commit: empty commit" output
1203 '
1204
1205 +test_expect_success 'skip an empty patch in the middle of an am session' '
1206 + git checkout empty-commit^ &&
1207 + test_must_fail git am empty-commit.patch >err &&
1208 + grep "Patch is empty." err &&
1209 + grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
1210 + git am --skip &&
1211 + test_path_is_missing .git/rebase-apply &&
1212 + git rev-parse empty-commit^ >expected &&
1213 + git rev-parse HEAD >actual &&
1214 + test_cmp expected actual
1215 +'
1216 +
1217 +test_expect_success 'record an empty patch as an empty commit in the middle of an am session' '
1218 + git checkout empty-commit^ &&
1219 + test_must_fail git am empty-commit.patch >err &&
1220 + grep "Patch is empty." err &&
1221 + grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
1222 + git am --allow-empty >output &&
1223 + grep "No changes - recorded it as an empty commit." output &&
1224 + test_path_is_missing .git/rebase-apply &&
1225 + git show empty-commit --format="%B" >expected &&
1226 + git show HEAD --format="%B" >actual &&
1227 + grep -f actual expected
1228 +'
1229 +
1230 +test_expect_success 'create an non-empty commit when the index IS changed though "--allow-empty" is given' '
1231 + git checkout empty-commit^ &&
1232 + test_must_fail git am empty-commit.patch >err &&
1233 + : >empty-file &&
1234 + git add empty-file &&
1235 + git am --allow-empty &&
1236 + git show empty-commit --format="%B" >expected &&
1237 + git show HEAD --format="%B" >actual &&
1238 + grep -f actual expected &&
1239 + git diff HEAD^..HEAD --name-only
1240 +'
1241 +
1242 +test_expect_success 'cannot create empty commits when there is a clean index due to merge conflicts' '
1243 + test_when_finished "git am --abort || :" &&
1244 + git rev-parse HEAD >expected &&
1245 + test_must_fail git am seq.patch &&
1246 + test_must_fail git am --allow-empty >err &&
1247 + ! grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
1248 + git rev-parse HEAD >actual &&
1249 + test_cmp actual expected
1250 +'
1251 +
1252 +test_expect_success 'cannot create empty commits when there is unmerged index due to merge conflicts' '
1253 + test_when_finished "git am --abort || :" &&
1254 + git rev-parse HEAD >expected &&
1255 + test_must_fail git am -3 seq.patch &&
1256 + test_must_fail git am --allow-empty >err &&
1257 + ! grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err &&
1258 + git rev-parse HEAD >actual &&
1259 + test_cmp actual expected
1260 +'
1261 +
1262 test_done
t/t7512-status-help.sh
+1
@@ -659,6 +659,7 @@ On branch am_empty
659 You are in the middle of an am session.
660 The current patch is empty.
661 (use "git am --skip" to skip this patch)
662 + (use "git am --allow-empty" to record this patch as an empty commit)
663 (use "git am --abort" to restore the original branch)
664
665 nothing to commit (use -u to show untracked files)
wt-status.c
+7 -1
@@ -1212,17 +1212,23 @@ static void show_merge_in_progress(struct wt_status *s,
1212 static void show_am_in_progress(struct wt_status *s,
1213 const char *color)
1214 {
1215 + int am_empty_patch;
1216 +
1217 status_printf_ln(s, color,
1218 _("You are in the middle of an am session."));
1219 if (s->state.am_empty_patch)
1220 status_printf_ln(s, color,
1221 _("The current patch is empty."));
1222 if (s->hints) {
1221 - if (!s->state.am_empty_patch)
1223 + am_empty_patch = s->state.am_empty_patch;
1224 + if (!am_empty_patch)
1225 status_printf_ln(s, color,
1226 _(" (fix conflicts and then run \"git am --continue\")"));
1227 status_printf_ln(s, color,
1228 _(" (use \"git am --skip\" to skip this patch)"));
1229 + if (am_empty_patch)
1230 + status_printf_ln(s, color,
1231 + _(" (use \"git am --allow-empty\" to record this patch as an empty commit)"));
1232 status_printf_ln(s, color,
1233 _(" (use \"git am --abort\" to restore the original branch)"));
1234 }