@@ -105,7 +105,8 @@ static const char *template_file;
105105 */
106106static const char * author_message , * author_message_buffer ;
107107static char * edit_message , * use_message ;
108- static char * fixup_message , * squash_message ;
108+ static char * fixup_message , * fixup_commit , * squash_message ;
109+ static const char * fixup_prefix ;
109110static int all , also , interactive , patch_interactive , only , amend , signoff ;
110111static int edit_flag = -1 ; /* unspecified */
111112static int quiet , verbose , no_verify , allow_empty , dry_run , renew_authorship ;
@@ -357,7 +358,8 @@ static const char *prepare_index(const char **argv, const char *prefix,
357358 die (_ ("--pathspec-file-nul requires --pathspec-from-file" ));
358359 }
359360
360- if (!pathspec .nr && (also || (only && !amend && !allow_empty )))
361+ if (!pathspec .nr && (also || (only && !allow_empty &&
362+ (!amend || (fixup_message && strcmp (fixup_prefix , "amend" ))))))
361363 die (_ ("No paths with --include/--only does not make sense." ));
362364
363365 if (read_cache_preload (& pathspec ) < 0 )
@@ -681,6 +683,22 @@ static void adjust_comment_line_char(const struct strbuf *sb)
681683 comment_line_char = * p ;
682684}
683685
686+ static void prepare_amend_commit (struct commit * commit , struct strbuf * sb ,
687+ struct pretty_print_context * ctx )
688+ {
689+ const char * buffer , * subject , * fmt ;
690+
691+ buffer = get_commit_buffer (commit , NULL );
692+ find_commit_subject (buffer , & subject );
693+ /*
694+ * If we amend the 'amend!' commit then we don't want to
695+ * duplicate the subject line.
696+ */
697+ fmt = starts_with (subject , "amend!" ) ? "%b" : "%B" ;
698+ format_commit_message (commit , fmt , sb , ctx );
699+ unuse_commit_buffer (commit , buffer );
700+ }
701+
684702static int prepare_to_commit (const char * index_file , const char * prefix ,
685703 struct commit * current_head ,
686704 struct wt_status * s ,
@@ -745,15 +763,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
745763 } else if (fixup_message ) {
746764 struct pretty_print_context ctx = {0 };
747765 struct commit * commit ;
748- commit = lookup_commit_reference_by_name (fixup_message );
766+ char * fmt ;
767+ commit = lookup_commit_reference_by_name (fixup_commit );
749768 if (!commit )
750- die (_ ("could not lookup commit %s" ), fixup_message );
769+ die (_ ("could not lookup commit %s" ), fixup_commit );
751770 ctx .output_encoding = get_commit_output_encoding ();
752- format_commit_message (commit , "fixup! %s\n\n" ,
753- & sb , & ctx );
754- if (have_option_m )
755- strbuf_addbuf (& sb , & message );
771+ fmt = xstrfmt ("%s! %%s\n\n" , fixup_prefix );
772+ format_commit_message (commit , fmt , & sb , & ctx );
773+ free (fmt );
756774 hook_arg1 = "message" ;
775+
776+ /*
777+ * Only `-m` commit message option is checked here, as
778+ * it supports `--fixup` to append the commit message.
779+ *
780+ * The other commit message options `-c`/`-C`/`-F` are
781+ * incompatible with all the forms of `--fixup` and
782+ * have already errored out while parsing the `git commit`
783+ * options.
784+ */
785+ if (have_option_m && !strcmp (fixup_prefix , "fixup" ))
786+ strbuf_addbuf (& sb , & message );
787+
788+ if (!strcmp (fixup_prefix , "amend" )) {
789+ if (have_option_m )
790+ die (_ ("cannot combine -m with --fixup:%s" ), fixup_message );
791+ prepare_amend_commit (commit , & sb , & ctx );
792+ }
757793 } else if (!stat (git_path_merge_msg (the_repository ), & statbuf )) {
758794 size_t merge_msg_start ;
759795
@@ -1152,6 +1188,19 @@ static void finalize_deferred_config(struct wt_status *s)
11521188 s -> ahead_behind_flags = AHEAD_BEHIND_FULL ;
11531189}
11541190
1191+ static void check_fixup_reword_options (int argc , const char * argv []) {
1192+ if (whence != FROM_COMMIT ) {
1193+ if (whence == FROM_MERGE )
1194+ die (_ ("You are in the middle of a merge -- cannot reword." ));
1195+ else if (is_from_cherry_pick (whence ))
1196+ die (_ ("You are in the middle of a cherry-pick -- cannot reword." ));
1197+ }
1198+ if (argc )
1199+ die (_ ("cannot combine reword option of --fixup with path '%s'" ), * argv );
1200+ if (patch_interactive || interactive || all || also || only )
1201+ die (_ ("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only" ));
1202+ }
1203+
11551204static int parse_and_validate_options (int argc , const char * argv [],
11561205 const struct option * options ,
11571206 const char * const usage [],
@@ -1170,7 +1219,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
11701219 if (force_author && renew_authorship )
11711220 die (_ ("Using both --reset-author and --author does not make sense" ));
11721221
1173- if (logfile || have_option_m || use_message || fixup_message )
1222+ if (logfile || have_option_m || use_message )
11741223 use_editor = 0 ;
11751224 if (0 <= edit_flag )
11761225 use_editor = edit_flag ;
@@ -1227,6 +1276,42 @@ static int parse_and_validate_options(int argc, const char *argv[],
12271276
12281277 if (also + only + all + interactive > 1 )
12291278 die (_ ("Only one of --include/--only/--all/--interactive/--patch can be used." ));
1279+
1280+ if (fixup_message ) {
1281+ /*
1282+ * We limit --fixup's suboptions to only alpha characters.
1283+ * If the first character after a run of alpha is colon,
1284+ * then the part before the colon may be a known suboption
1285+ * name like `amend` or `reword`, or a misspelt suboption
1286+ * name. In either case, we treat it as
1287+ * --fixup=<suboption>:<arg>.
1288+ *
1289+ * Otherwise, we are dealing with --fixup=<commit>.
1290+ */
1291+ char * p = fixup_message ;
1292+ while (isalpha (* p ))
1293+ p ++ ;
1294+ if (p > fixup_message && * p == ':' ) {
1295+ * p = '\0' ;
1296+ fixup_commit = p + 1 ;
1297+ if (!strcmp ("amend" , fixup_message ) ||
1298+ !strcmp ("reword" , fixup_message )) {
1299+ fixup_prefix = "amend" ;
1300+ allow_empty = 1 ;
1301+ if (* fixup_message == 'r' ) {
1302+ check_fixup_reword_options (argc , argv );
1303+ only = 1 ;
1304+ }
1305+ } else {
1306+ die (_ ("unknown option: --fixup=%s:%s" ), fixup_message , fixup_commit );
1307+ }
1308+ } else {
1309+ fixup_commit = fixup_message ;
1310+ fixup_prefix = "fixup" ;
1311+ use_editor = 0 ;
1312+ }
1313+ }
1314+
12301315 cleanup_mode = get_cleanup_mode (cleanup_arg , use_editor );
12311316
12321317 handle_untracked_files_arg (s );
@@ -1504,7 +1589,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
15041589 OPT_CALLBACK ('m' , "message" , & message , N_ ("message" ), N_ ("commit message" ), opt_parse_m ),
15051590 OPT_STRING ('c' , "reedit-message" , & edit_message , N_ ("commit" ), N_ ("reuse and edit message from specified commit" )),
15061591 OPT_STRING ('C' , "reuse-message" , & use_message , N_ ("commit" ), N_ ("reuse message from specified commit" )),
1507- OPT_STRING (0 , "fixup" , & fixup_message , N_ ("commit" ), N_ ("use autosquash formatted message to fixup specified commit" )),
1592+ /*
1593+ * TRANSLATORS: Leave "[(amend|reword):]" as-is,
1594+ * and only translate <commit>.
1595+ */
1596+ OPT_STRING (0 , "fixup" , & fixup_message , N_ ("[(amend|reword):]commit" ), N_ ("use autosquash formatted message to fixup or amend/reword specified commit" )),
15081597 OPT_STRING (0 , "squash" , & squash_message , N_ ("commit" ), N_ ("use autosquash formatted message to squash specified commit" )),
15091598 OPT_BOOL (0 , "reset-author" , & renew_authorship , N_ ("the commit is authored by me now (used with -C/-c/--amend)" )),
15101599 OPT_BOOL ('s' , "signoff" , & signoff , N_ ("add a Signed-off-by trailer" )),
@@ -1663,6 +1752,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
16631752 exit (1 );
16641753 }
16651754
1755+ if (fixup_message && starts_with (sb .buf , "amend! " ) &&
1756+ !allow_empty_message ) {
1757+ struct strbuf body = STRBUF_INIT ;
1758+ size_t len = commit_subject_length (sb .buf );
1759+ strbuf_addstr (& body , sb .buf + len );
1760+ if (message_is_empty (& body , cleanup_mode )) {
1761+ rollback_index_files ();
1762+ fprintf (stderr , _ ("Aborting commit due to empty commit message body.\n" ));
1763+ exit (1 );
1764+ }
1765+ strbuf_release (& body );
1766+ }
1767+
16661768 if (amend ) {
16671769 const char * exclude_gpgsig [3 ] = { "gpgsig" , "gpgsig-sha256" , NULL };
16681770 extra = read_commit_extra_headers (current_head , exclude_gpgsig );
0 commit comments