3636#include "reset.h"
3737#include "trace2.h"
3838#include "hook.h"
39+ #include "trailer.h"
40+ #include "parse-options.h"
3941
4042static char const * const builtin_rebase_usage [] = {
4143 N_ ("git rebase [-i] [options] [--exec <cmd>] "
@@ -113,6 +115,7 @@ struct rebase_options {
113115 enum action action ;
114116 char * reflog_action ;
115117 int signoff ;
118+ struct strvec trailer_args ;
116119 int allow_rerere_autoupdate ;
117120 int keep_empty ;
118121 int autosquash ;
@@ -143,6 +146,7 @@ struct rebase_options {
143146 .flags = REBASE_NO_QUIET, \
144147 .git_am_opts = STRVEC_INIT, \
145148 .exec = STRING_LIST_INIT_NODUP, \
149+ .trailer_args = STRVEC_INIT, \
146150 .git_format_patch_opt = STRBUF_INIT, \
147151 .fork_point = -1, \
148152 .reapply_cherry_picks = -1, \
@@ -166,6 +170,7 @@ static void rebase_options_release(struct rebase_options *opts)
166170 free (opts -> strategy );
167171 string_list_clear (& opts -> strategy_opts , 0 );
168172 strbuf_release (& opts -> git_format_patch_opt );
173+ strvec_clear (& opts -> trailer_args );
169174}
170175
171176static struct replay_opts get_replay_opts (const struct rebase_options * opts )
@@ -177,6 +182,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
177182 sequencer_init_config (& replay );
178183
179184 replay .signoff = opts -> signoff ;
185+
186+ for (size_t i = 0 ; i < opts -> trailer_args .nr ; i ++ )
187+ strvec_push (& replay .trailer_args , opts -> trailer_args .v [i ]);
188+
180189 replay .allow_ff = !(opts -> flags & REBASE_FORCE );
181190 if (opts -> allow_rerere_autoupdate )
182191 replay .allow_rerere_auto = opts -> allow_rerere_autoupdate ;
@@ -435,6 +444,8 @@ static int read_basic_state(struct rebase_options *opts)
435444 struct strbuf head_name = STRBUF_INIT ;
436445 struct strbuf buf = STRBUF_INIT ;
437446 struct object_id oid ;
447+ const char trailer_state_name [] = "trailer" ;
448+ const char * path = state_dir_path (trailer_state_name , opts );
438449
439450 if (!read_oneliner (& head_name , state_dir_path ("head-name" , opts ),
440451 READ_ONELINER_WARN_MISSING ) ||
@@ -503,11 +514,31 @@ static int read_basic_state(struct rebase_options *opts)
503514
504515 strbuf_release (& buf );
505516
517+ if (strbuf_read_file (& buf , path , 0 ) >= 0 ) {
518+ const char * p = buf .buf , * end = buf .buf + buf .len ;
519+
520+ while (p < end ) {
521+ char * nl = memchr (p , '\n' , end - p );
522+ if (!nl )
523+ die ("nl shouldn't be NULL" );
524+ * nl = '\0' ;
525+
526+ if (* p )
527+ strvec_push (& opts -> trailer_args , p );
528+
529+ p = nl + 1 ;
530+ }
531+ strbuf_release (& buf );
532+ }
533+ strbuf_release (& buf );
534+
506535 return 0 ;
507536}
508537
509538static int rebase_write_basic_state (struct rebase_options * opts )
510539{
540+ const char trailer_state_name [] = "trailer" ;
541+
511542 write_file (state_dir_path ("head-name" , opts ), "%s" ,
512543 opts -> head_name ? opts -> head_name : "detached HEAD" );
513544 write_file (state_dir_path ("onto" , opts ), "%s" ,
@@ -529,6 +560,22 @@ static int rebase_write_basic_state(struct rebase_options *opts)
529560 if (opts -> signoff )
530561 write_file (state_dir_path ("signoff" , opts ), "--signoff" );
531562
563+ /*
564+ * save opts->trailer_args into state_dir/trailer
565+ */
566+ if (opts -> trailer_args .nr ) {
567+ struct strbuf buf = STRBUF_INIT ;
568+ size_t i ;
569+
570+ for (i = 0 ; i < opts -> trailer_args .nr ; i ++ ) {
571+ strbuf_addstr (& buf , opts -> trailer_args .v [i ]);
572+ strbuf_addch (& buf , '\n' );
573+ }
574+ write_file (state_dir_path (trailer_state_name , opts ),
575+ "%s" , buf .buf );
576+ strbuf_release (& buf );
577+ }
578+
532579 return 0 ;
533580}
534581
@@ -1085,6 +1132,37 @@ static int check_exec_cmd(const char *cmd)
10851132 return 0 ;
10861133}
10871134
1135+ static int validate_trailer_args_after_config (const struct strvec * cli_args ,
1136+ struct strbuf * err )
1137+ {
1138+ size_t i ;
1139+
1140+ for (i = 0 ; i < cli_args -> nr ; i ++ ) {
1141+ const char * raw = cli_args -> v [i ];
1142+ const char * txt ; // Key[:=]Val
1143+ const char * sep ;
1144+
1145+ if (!skip_prefix (raw , "--trailer=" , & txt ))
1146+ txt = raw ;
1147+
1148+ if (!* txt ) {
1149+ strbuf_addstr (err , _ ("empty --trailer argument" ));
1150+ return -1 ;
1151+ }
1152+
1153+ sep = strpbrk (txt , ":=" );
1154+
1155+ /* there must be key bfore seperator */
1156+ if (sep && sep == txt ) {
1157+ strbuf_addf (err ,
1158+ _ ("invalid trailer '%s': missing key before separator" ),
1159+ txt );
1160+ return -1 ;
1161+ }
1162+ }
1163+ return 0 ;
1164+ }
1165+
10881166int cmd_rebase (int argc ,
10891167 const char * * argv ,
10901168 const char * prefix ,
@@ -1133,6 +1211,7 @@ int cmd_rebase(int argc,
11331211 .flags = PARSE_OPT_NOARG ,
11341212 .defval = REBASE_DIFFSTAT ,
11351213 },
1214+ OPT_STRVEC (0 , "trailer" , & options .trailer_args , N_ ("trailer" ), N_ ("add custom trailer(s)" )),
11361215 OPT_BOOL (0 , "signoff" , & options .signoff ,
11371216 N_ ("add a Signed-off-by trailer to each commit" )),
11381217 OPT_BOOL (0 , "committer-date-is-author-date" ,
@@ -1286,6 +1365,17 @@ int cmd_rebase(int argc,
12861365 builtin_rebase_options ,
12871366 builtin_rebase_usage , 0 );
12881367
1368+ /* if add --trailer,force rebase */
1369+ if (options .trailer_args .nr ) {
1370+ struct strbuf err = STRBUF_INIT ;
1371+
1372+ if (validate_trailer_args_after_config (& options .trailer_args , & err ))
1373+ die ("%s" , err .buf );
1374+
1375+ options .flags |= REBASE_FORCE ;
1376+ strbuf_release (& err );
1377+ }
1378+
12891379 if (preserve_merges_selected )
12901380 die (_ ("--preserve-merges was replaced by --rebase-merges\n"
12911381 "Note: Your `pull.rebase` configuration may also be set to 'preserve',\n"
@@ -1543,6 +1633,14 @@ int cmd_rebase(int argc,
15431633 if (options .root && !options .onto_name )
15441634 imply_merge (& options , "--root without --onto" );
15451635
1636+ /*
1637+ * The apply‑based backend (git am) cannot append trailers because
1638+ * it lacks a message‑filter facility. Reject early, before any
1639+ * state (index, HEAD, etc.) is modified.
1640+ */
1641+ if (options .trailer_args .nr )
1642+ imply_merge (& options , "--trailer" );
1643+
15461644 if (isatty (2 ) && options .flags & REBASE_NO_QUIET )
15471645 strbuf_addstr (& options .git_format_patch_opt , " --progress" );
15481646
0 commit comments