36
36
#include "reset.h"
37
37
#include "trace2.h"
38
38
#include "hook.h"
39
+ #include "trailer.h"
40
+ #include "parse-options.h"
39
41
40
42
static char const * const builtin_rebase_usage [] = {
41
43
N_ ("git rebase [-i] [options] [--exec <cmd>] "
@@ -113,6 +115,7 @@ struct rebase_options {
113
115
enum action action ;
114
116
char * reflog_action ;
115
117
int signoff ;
118
+ struct strvec trailer_args ;
116
119
int allow_rerere_autoupdate ;
117
120
int keep_empty ;
118
121
int autosquash ;
@@ -143,6 +146,7 @@ struct rebase_options {
143
146
.flags = REBASE_NO_QUIET, \
144
147
.git_am_opts = STRVEC_INIT, \
145
148
.exec = STRING_LIST_INIT_NODUP, \
149
+ .trailer_args = STRVEC_INIT, \
146
150
.git_format_patch_opt = STRBUF_INIT, \
147
151
.fork_point = -1, \
148
152
.reapply_cherry_picks = -1, \
@@ -166,6 +170,7 @@ static void rebase_options_release(struct rebase_options *opts)
166
170
free (opts -> strategy );
167
171
string_list_clear (& opts -> strategy_opts , 0 );
168
172
strbuf_release (& opts -> git_format_patch_opt );
173
+ strvec_clear (& opts -> trailer_args );
169
174
}
170
175
171
176
static 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)
177
182
sequencer_init_config (& replay );
178
183
179
184
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
+
180
189
replay .allow_ff = !(opts -> flags & REBASE_FORCE );
181
190
if (opts -> allow_rerere_autoupdate )
182
191
replay .allow_rerere_auto = opts -> allow_rerere_autoupdate ;
@@ -435,6 +444,8 @@ static int read_basic_state(struct rebase_options *opts)
435
444
struct strbuf head_name = STRBUF_INIT ;
436
445
struct strbuf buf = STRBUF_INIT ;
437
446
struct object_id oid ;
447
+ const char trailer_state_name [] = "trailer" ;
448
+ const char * path = state_dir_path (trailer_state_name , opts );
438
449
439
450
if (!read_oneliner (& head_name , state_dir_path ("head-name" , opts ),
440
451
READ_ONELINER_WARN_MISSING ) ||
@@ -503,11 +514,31 @@ static int read_basic_state(struct rebase_options *opts)
503
514
504
515
strbuf_release (& buf );
505
516
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
+
506
535
return 0 ;
507
536
}
508
537
509
538
static int rebase_write_basic_state (struct rebase_options * opts )
510
539
{
540
+ const char trailer_state_name [] = "trailer" ;
541
+
511
542
write_file (state_dir_path ("head-name" , opts ), "%s" ,
512
543
opts -> head_name ? opts -> head_name : "detached HEAD" );
513
544
write_file (state_dir_path ("onto" , opts ), "%s" ,
@@ -529,6 +560,22 @@ static int rebase_write_basic_state(struct rebase_options *opts)
529
560
if (opts -> signoff )
530
561
write_file (state_dir_path ("signoff" , opts ), "--signoff" );
531
562
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
+
532
579
return 0 ;
533
580
}
534
581
@@ -1085,6 +1132,37 @@ static int check_exec_cmd(const char *cmd)
1085
1132
return 0 ;
1086
1133
}
1087
1134
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
+
1088
1166
int cmd_rebase (int argc ,
1089
1167
const char * * argv ,
1090
1168
const char * prefix ,
@@ -1133,6 +1211,7 @@ int cmd_rebase(int argc,
1133
1211
.flags = PARSE_OPT_NOARG ,
1134
1212
.defval = REBASE_DIFFSTAT ,
1135
1213
},
1214
+ OPT_STRVEC (0 , "trailer" , & options .trailer_args , N_ ("trailer" ), N_ ("add custom trailer(s)" )),
1136
1215
OPT_BOOL (0 , "signoff" , & options .signoff ,
1137
1216
N_ ("add a Signed-off-by trailer to each commit" )),
1138
1217
OPT_BOOL (0 , "committer-date-is-author-date" ,
@@ -1283,6 +1362,17 @@ int cmd_rebase(int argc,
1283
1362
builtin_rebase_options ,
1284
1363
builtin_rebase_usage , 0 );
1285
1364
1365
+ /* if add --trailer,force rebase */
1366
+ if (options .trailer_args .nr ) {
1367
+ struct strbuf err = STRBUF_INIT ;
1368
+
1369
+ if (validate_trailer_args_after_config (& options .trailer_args , & err ))
1370
+ die ("%s" , err .buf );
1371
+
1372
+ options .flags |= REBASE_FORCE ;
1373
+ strbuf_release (& err );
1374
+ }
1375
+
1286
1376
if (preserve_merges_selected )
1287
1377
die (_ ("--preserve-merges was replaced by --rebase-merges\n"
1288
1378
"Note: Your `pull.rebase` configuration may also be set to 'preserve',\n"
@@ -1540,6 +1630,14 @@ int cmd_rebase(int argc,
1540
1630
if (options .root && !options .onto_name )
1541
1631
imply_merge (& options , "--root without --onto" );
1542
1632
1633
+ /*
1634
+ * The apply‑based backend (git am) cannot append trailers because
1635
+ * it lacks a message‑filter facility. Reject early, before any
1636
+ * state (index, HEAD, etc.) is modified.
1637
+ */
1638
+ if (options .trailer_args .nr )
1639
+ imply_merge (& options , "--trailer" );
1640
+
1543
1641
if (isatty (2 ) && options .flags & REBASE_NO_QUIET )
1544
1642
strbuf_addstr (& options .git_format_patch_opt , " --progress" );
1545
1643
0 commit comments